package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type SeriesIntegrationTestSuite struct {
	suite.Suite
	setup     *TestServerSetup
	token     string
	libraryID string
}

func (s *SeriesIntegrationTestSuite) SetupSuite() {
	s.setup = setupTestServer(s.T())
	s.token = s.setup.Token
	s.libraryID = createTestLibraryWithFolder(s.T(), s.setup.Server, s.token, "Test Series Library", false)
}

func (s *SeriesIntegrationTestSuite) TearDownSuite() {
	s.setup.Close()
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_RequiresLibraryID() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series", nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)

	var body map[string]string
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), "library_id required", body["error"])
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_InvalidLibraryID() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series?library_id=not-a-uuid", nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)

	var body map[string]string
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), "invalid library_id", body["error"])
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_EmptyLibrary() {
	url := fmt.Sprintf("%s/api/series?library_id=%s", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)

	series, ok := body["series"].([]interface{})
	require.True(s.T(), ok, "series should be an array")
	assert.Empty(s.T(), series, "empty library should have no series")

	total, ok := body["total"].(float64)
	require.True(s.T(), ok, "total should be a number")
	assert.Equal(s.T(), float64(0), total)
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_Unauthorized() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series?library_id="+s.libraryID, nil)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode)
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_PaginationParams() {
	url := fmt.Sprintf("%s/api/series?library_id=%s&limit=5&offset=0", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)

	limit, ok := body["limit"].(float64)
	require.True(s.T(), ok)
	assert.Equal(s.T(), float64(5), limit)

	offset, ok := body["offset"].(float64)
	require.True(s.T(), ok)
	assert.Equal(s.T(), float64(0), offset)
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_LimitClampedTo100() {
	url := fmt.Sprintf("%s/api/series?library_id=%s&limit=999", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)

	limit, ok := body["limit"].(float64)
	require.True(s.T(), ok)
	assert.Equal(s.T(), float64(100), limit, "limit should be clamped to 100")
}

func (s *SeriesIntegrationTestSuite) TestGetSeriesBooks_RequiresLibraryID() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series/books?name=Test+Series", nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)

	var body map[string]string
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), "library_id required", body["error"])
}

func (s *SeriesIntegrationTestSuite) TestGetSeriesBooks_RequiresName() {
	url := fmt.Sprintf("%s/api/series/books?library_id=%s", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)

	var body map[string]string
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), "name required", body["error"])
}

func (s *SeriesIntegrationTestSuite) TestGetSeriesBooks_InvalidLibraryID() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series/books?library_id=bad-uuid&name=Test", nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)
}

func (s *SeriesIntegrationTestSuite) TestGetSeriesBooks_NonexistentSeries() {
	url := fmt.Sprintf("%s/api/series/books?library_id=%s&name=Nonexistent+Series", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)

	books, ok := body["books"].([]interface{})
	require.True(s.T(), ok, "books should be an array")
	assert.Empty(s.T(), books, "nonexistent series should return empty books array")

	assert.Equal(s.T(), "Nonexistent Series", body["name"])
	total, ok := body["total"].(float64)
	require.True(s.T(), ok)
	assert.Equal(s.T(), float64(0), total)
}

func (s *SeriesIntegrationTestSuite) TestGetSeriesBooks_Unauthorized() {
	req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/series/books?library_id="+s.libraryID+"&name=Test", nil)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode)
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_SpecialCharactersInName() {
	seriesName := "Series: Book & Other (Vol. 1)"
	url := fmt.Sprintf("%s/api/series/books?library_id=%s&name=%s", s.setup.Server.URL, s.libraryID, url.QueryEscape(seriesName))
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)
}

func (s *SeriesIntegrationTestSuite) TestGetSeries_ResponseStructure() {
	url := fmt.Sprintf("%s/api/series?library_id=%s", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(s.T(), err)

	assert.Contains(s.T(), body, "series", "response should contain 'series' key")
	assert.Contains(s.T(), body, "total", "response should contain 'total' key")
	assert.Contains(s.T(), body, "limit", "response should contain 'limit' key")
	assert.Contains(s.T(), body, "offset", "response should contain 'offset' key")

	_, ok := body["series"].([]interface{})
	assert.True(s.T(), ok, "'series' should be an array")
}

func (s *SeriesIntegrationTestSuite) TestRestoreSystemCollection_ContinueSeries() {
	reqBody := map[string]interface{}{
		"collection_name": "Continue Series",
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", s.setup.Server.URL+"/api/dashboard/restore-system-collection", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var response map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&response)
	require.NoError(s.T(), err)
	assert.Contains(s.T(), response, "message")
}

func (s *SeriesIntegrationTestSuite) TestGetSections_IncludesContinueSeries() {
	url := fmt.Sprintf("%s/api/dashboard/sections?library_id=%s", s.setup.Server.URL, s.libraryID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+s.token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	assert.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var response map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&response)
	require.NoError(s.T(), err)

	sections, ok := response["sections"].([]interface{})
	require.True(s.T(), ok, "sections should be an array")
	require.Len(s.T(), sections, 5, "Should have 5 system collections")

	sectionIDs := make(map[string]bool)
	for _, sec := range sections {
		section := sec.(map[string]interface{})
		sectionIDs[section["id"].(string)] = true
	}

	assert.Contains(s.T(), sectionIDs, "continue-series", "dashboard should include continue-series section")
	assert.Contains(s.T(), sectionIDs, "continue-reading")
	assert.Contains(s.T(), sectionIDs, "recently-added")
	assert.Contains(s.T(), sectionIDs, "recently-read")
	assert.Contains(s.T(), sectionIDs, "not-started")
}

func TestSeriesIntegrationTestSuite(t *testing.T) {
	suite.Run(t, new(SeriesIntegrationTestSuite))
}
