package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
	"time"

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

type ScannerIntegrationTestSuite struct {
	suite.Suite
	setup *TestServerSetup
}

func (s *ScannerIntegrationTestSuite) SetupSuite() {
	s.setup = setupTestServer(s.T())
}

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

func (s *ScannerIntegrationTestSuite) TestScanProgress_TracksStatistics() {
	token := s.setup.Token

	createLibReq := map[string]interface{}{
		"name":        "Scan Test Library",
		"description": "Test library for scan progress",
		"type":        "ebooks",
	}
	createLibBody, _ := json.Marshal(createLibReq)
	createLibURL := s.setup.Server.URL + "/api/libraries"
	createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(createLibBody))
	createLibReqHTTP.Header.Set("Content-Type", "application/json")
	createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createLibResp, err := client.Do(createLibReqHTTP)
	require.NoError(s.T(), err)
	require.Equal(s.T(), http.StatusCreated, createLibResp.StatusCode)

	var createLibResponse map[string]interface{}
	err = json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
	require.NoError(s.T(), err)
	_ = createLibResp.Body.Close()

	libraryID, ok := createLibResponse["id"].(string)
	require.True(s.T(), ok, "library_id should be string")
	require.NotEmpty(s.T(), libraryID, "library_id should not be empty")

	folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", s.setup.Server.URL, libraryID)
	folderReq := map[string]interface{}{
		"folder_path": "/app/uploads",
	}
	folderBody, _ := json.Marshal(folderReq)
	req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(folderBody))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	_ = resp.Body.Close()
	require.Equal(s.T(), http.StatusCreated, resp.StatusCode, "Folder creation should succeed")

	scanURL := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
	scanReq, _ := http.NewRequest("POST", scanURL, nil)
	scanReq.Header.Set("Authorization", "Bearer "+token)

	scanResp, err := client.Do(scanReq)
	require.NoError(s.T(), err)
	require.Equal(s.T(), http.StatusAccepted, scanResp.StatusCode)

	var scanResponse map[string]interface{}
	err = json.NewDecoder(scanResp.Body).Decode(&scanResponse)
	require.NoError(s.T(), err)
	_ = scanResp.Body.Close()

	jobID, ok := scanResponse["job_id"].(string)
	require.True(s.T(), ok, "job_id should be string")
	require.NotEmpty(s.T(), jobID, "job_id should not be empty")

	// Give scan time to complete before polling
	time.Sleep(3 * time.Second)

	var lastProgress float64
	var lastFilesScanned, lastNewItems, lastErrors int
	gotProgressUpdate := false

	for i := 0; i < 30; i++ {
		if i > 0 {
			time.Sleep(1 * time.Second)
		}

		statusURL := fmt.Sprintf("%s/api/scanner/status/%s", s.setup.Server.URL, jobID)
		statusReq, _ := http.NewRequest("GET", statusURL, nil)
		statusReq.Header.Set("Authorization", "Bearer "+token)

		statusResp, err := client.Do(statusReq)
		require.NoError(s.T(), err)

		if statusResp.StatusCode == http.StatusNotFound {
			_ = statusResp.Body.Close()
			if gotProgressUpdate {
				break
			}
			continue
		}

		var status map[string]interface{}
		err = json.NewDecoder(statusResp.Body).Decode(&status)
		_ = statusResp.Body.Close()
		require.NoError(s.T(), err)

		if _, hasError := status["error"]; hasError {
			continue
		}

		assert.Contains(s.T(), status, "files_scanned")
		assert.Contains(s.T(), status, "new_items")
		assert.Contains(s.T(), status, "errors")

		progressFloat, ok := status["progress"].(float64)
		require.True(s.T(), ok, "progress should be float64")
		progress := progressFloat

		filesScannedFloat, ok := status["files_scanned"].(float64)
		require.True(s.T(), ok, "files_scanned should be float64")
		filesScanned := int(filesScannedFloat)

		newItemsFloat, ok := status["new_items"].(float64)
		require.True(s.T(), ok, "new_items should be float64")
		newItems := int(newItemsFloat)

		errorsFloat, ok := status["errors"].(float64)
		require.True(s.T(), ok, "errors should be float64")
		errors := int(errorsFloat)

		assert.GreaterOrEqual(s.T(), progress, lastProgress)
		lastProgress = progress
		gotProgressUpdate = true

		assert.GreaterOrEqual(s.T(), filesScanned, lastFilesScanned)
		lastFilesScanned = filesScanned

		assert.GreaterOrEqual(s.T(), newItems, lastNewItems)
		assert.GreaterOrEqual(s.T(), errors, lastErrors)

		if status["status"] == "completed" || status["status"] == "failed" {
			break
		}
	}

	if gotProgressUpdate {
		assert.Equal(s.T(), 1.0, lastProgress)
		assert.GreaterOrEqual(s.T(), lastFilesScanned, 0)
	}
}

func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
	token := s.setup.Token

	createLibReq := map[string]interface{}{
		"name":        "Batch Test Library",
		"description": "Test library for batching",
		"type":        "ebooks",
	}
	createLibBody, _ := json.Marshal(createLibReq)
	createLibURL := s.setup.Server.URL + "/api/libraries"
	createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(createLibBody))
	createLibReqHTTP.Header.Set("Content-Type", "application/json")
	createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createLibResp, err := client.Do(createLibReqHTTP)
	require.NoError(s.T(), err)

	var createLibResponse map[string]interface{}
	err = json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
	require.NoError(s.T(), err)
	_ = createLibResp.Body.Close()

	libraryID, ok := createLibResponse["id"].(string)
	require.True(s.T(), ok, "library_id should be string")
	require.NotEmpty(s.T(), libraryID, "library_id should not be empty")

	folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", s.setup.Server.URL, libraryID)
	folderReq := map[string]interface{}{
		"folder_path": "/app/uploads",
	}
	folderBody, _ := json.Marshal(folderReq)
	req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(folderBody))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := client.Do(req)
	require.NoError(s.T(), err)
	_ = resp.Body.Close()

	scanURL := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
	scanReq, _ := http.NewRequest("POST", scanURL, nil)
	scanReq.Header.Set("Authorization", "Bearer "+token)

	scanResp, err := client.Do(scanReq)
	require.NoError(s.T(), err)

	var scanResponse map[string]interface{}
	err = json.NewDecoder(scanResp.Body).Decode(&scanResponse)
	require.NoError(s.T(), err)
	_ = scanResp.Body.Close()

	jobID, ok := scanResponse["job_id"].(string)
	require.True(s.T(), ok, "job_id should be string")
	require.NotEmpty(s.T(), jobID, "job_id should not be empty")

	updateCount := 0
	previousFilesScanned := -1

	for i := 0; i < 20; i++ {
		if i > 0 {
			time.Sleep(500 * time.Millisecond)
		}

		statusURL := fmt.Sprintf("%s/api/scanner/status/%s", s.setup.Server.URL, jobID)
		statusReq, _ := http.NewRequest("GET", statusURL, nil)
		statusReq.Header.Set("Authorization", "Bearer "+token)

		statusResp, err := client.Do(statusReq)
		require.NoError(s.T(), err)

		if statusResp.StatusCode == http.StatusNotFound {
			_ = statusResp.Body.Close()
			break
		}

		var status map[string]interface{}
		err = json.NewDecoder(statusResp.Body).Decode(&status)
		require.NoError(s.T(), err)
		_ = statusResp.Body.Close()

		if _, hasError := status["error"]; hasError {
			continue
		}

		filesScannedFloat, ok := status["files_scanned"].(float64)
		require.True(s.T(), ok, "files_scanned should be float64")
		filesScanned := int(filesScannedFloat)

		if filesScanned != previousFilesScanned {
			updateCount++
			previousFilesScanned = filesScanned
		}

		if status["status"] == "completed" || status["status"] == "failed" {
			break
		}
	}

	assert.Less(s.T(), updateCount, 50)
}

func TestScannerIntegrationTestSuite(t *testing.T) {
	suite.Run(t, new(ScannerIntegrationTestSuite))
}
