package main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

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

// TestProcessingIssuesListAuthentication tests authentication and authorization for list endpoint
func TestProcessingIssuesListAuthentication(t *testing.T) {
	setup := setupTestServer(t)

	// Create a test library
	deviceSetup := setupDeviceTest(t)
	libraryID := deviceSetup.CreateLibrary(t, "Test Auth Library", "manga")

	t.Run("GET /api/libraries/:id/issues/list - No authentication", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("GET /api/libraries/:id/issues/list - Non-admin user forbidden", func(t *testing.T) {
		token := setup.RegularToken

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusForbidden, rec.Code)
	})

	t.Run("GET /api/libraries/:id/issues/list - Admin user authorized", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)
	})
}

// TestProcessingIssuesListResponseStructure tests the response structure of list endpoint
func TestProcessingIssuesListResponseStructure(t *testing.T) {
	setup := setupTestServer(t)

	// Create a test library
	deviceSetup := setupDeviceTest(t)
	libraryID := deviceSetup.CreateLibrary(t, "Test Structure Library", "comics")

	t.Run("Returns JSON array for empty library", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)

		var response []map[string]interface{}
		err := json.NewDecoder(rec.Body).Decode(&response)
		require.NoError(t, err)
		assert.IsType(t, []map[string]interface{}{}, response)
		assert.Equal(t, 0, len(response), "Empty library should return empty array")
	})

	t.Run("Has correct content type", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)
		assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
	})
}

// TestProcessingIssuesListInputValidation tests input validation for list endpoint
func TestProcessingIssuesListInputValidation(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	testCases := []struct {
		name           string
		libraryID      string
		expectedStatus int
		description    string
	}{
		{
			name:           "Invalid UUID format",
			libraryID:      "not-a-uuid",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject malformed UUID",
		},
		{
			name:           "Empty UUID",
			libraryID:      "",
			expectedStatus: http.StatusBadRequest,
			description:    "Should return 400 for empty ID",
		},
		{
			name:           "UUID with extra path traversal",
			libraryID:      "00000000-0000-0000-0000-000000000000/../../etc",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject path traversal attempts",
		},
		{
			name:           "Nonexistent library UUID",
			libraryID:      "00000000-0000-0000-0000-000000000000",
			expectedStatus: http.StatusOK,
			description:    "Should return empty array for valid but nonexistent UUID",
		},
		{
			name:           "Special characters in UUID",
			libraryID:      "'; DROP TABLE processing_issues; --",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject SQL injection attempts",
		},
		{
			name:           "Very long UUID",
			libraryID:      "00000000-0000-0000-0000-000000000000-extra-suffix",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject overly long IDs",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", "/api/libraries/"+url.PathEscape(tc.libraryID)+"/issues/list", nil)
			req.Header.Set("Authorization", "Bearer "+token)
			rec := httptest.NewRecorder()

			setup.Server.Config.Handler.ServeHTTP(rec, req)
			assert.Equal(t, tc.expectedStatus, rec.Code, tc.description)

			// For successful requests, verify response structure
			if tc.expectedStatus == http.StatusOK {
				var response []map[string]interface{}
				err := json.NewDecoder(rec.Body).Decode(&response)
				require.NoError(t, err)
				assert.IsType(t, []map[string]interface{}{}, response)
			}
		})
	}
}

// TestProcessingIssueStatsAuthentication tests authentication and authorization for stats endpoint
func TestProcessingIssueStatsAuthentication(t *testing.T) {
	setup := setupTestServer(t)

	// Create a test library
	deviceSetup := setupDeviceTest(t)
	libraryID := deviceSetup.CreateLibrary(t, "Test Stats Auth Library", "manga")

	t.Run("GET /api/libraries/:id/issues/stats - No authentication", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("GET /api/libraries/:id/issues/stats - Non-admin user forbidden", func(t *testing.T) {
		token := setup.RegularToken

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusForbidden, rec.Code)
	})

	t.Run("GET /api/libraries/:id/issues/stats - Admin user authorized", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)
	})
}

// TestProcessingIssueStatsResponseStructure tests the response structure of stats endpoint
func TestProcessingIssueStatsResponseStructure(t *testing.T) {
	setup := setupTestServer(t)

	// Create a test library
	deviceSetup := setupDeviceTest(t)
	libraryID := deviceSetup.CreateLibrary(t, "Test Stats Structure Library", "comics")

	t.Run("Returns correct structure for empty library", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)

		var response map[string]interface{}
		err := json.NewDecoder(rec.Body).Decode(&response)
		require.NoError(t, err)

		// Verify required fields
		assert.Contains(t, response, "error_count", "Should have error_count field")
		assert.Contains(t, response, "warning_count", "Should have warning_count field")
		assert.Contains(t, response, "info_count", "Should have info_count field")

		// Verify all counts are zero for empty library
		assert.Equal(t, float64(0), response["error_count"], "error_count should be 0")
		assert.Equal(t, float64(0), response["warning_count"], "warning_count should be 0")
		assert.Equal(t, float64(0), response["info_count"], "info_count should be 0")

		// Verify field types
		assert.IsType(t, float64(0), response["error_count"], "error_count should be number")
		assert.IsType(t, float64(0), response["warning_count"], "warning_count should be number")
		assert.IsType(t, float64(0), response["info_count"], "info_count should be number")
	})

	t.Run("Has correct content type", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)
		assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
	})

	t.Run("Has exactly 3 fields", func(t *testing.T) {
		token := setup.Token

		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)

		var response map[string]interface{}
		err := json.NewDecoder(rec.Body).Decode(&response)
		require.NoError(t, err)
		assert.Len(t, response, 3, "Should have exactly 3 fields")
	})
}

// TestProcessingIssueStatsInputValidation tests input validation for stats endpoint
func TestProcessingIssueStatsInputValidation(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	testCases := []struct {
		name           string
		libraryID      string
		expectedStatus int
		description    string
	}{
		{
			name:           "Invalid UUID format",
			libraryID:      "not-a-uuid",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject malformed UUID",
		},
		{
			name:           "Empty UUID",
			libraryID:      "",
			expectedStatus: http.StatusBadRequest,
			description:    "Should return 400 for empty ID",
		},
		{
			name:           "UUID with extra path traversal",
			libraryID:      "00000000-0000-0000-0000-000000000000/../../etc",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject path traversal attempts",
		},
		{
			name:           "Nonexistent library UUID",
			libraryID:      "00000000-0000-0000-0000-000000000000",
			expectedStatus: http.StatusOK,
			description:    "Should return zero counts for valid but nonexistent UUID",
		},
		{
			name:           "Special characters in UUID",
			libraryID:      "'; DROP TABLE processing_issues; --",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject SQL injection attempts",
		},
		{
			name:           "Very long UUID",
			libraryID:      "00000000-0000-0000-0000-000000000000-extra-suffix",
			expectedStatus: http.StatusBadRequest,
			description:    "Should reject overly long IDs",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", "/api/libraries/"+url.PathEscape(tc.libraryID)+"/issues/stats", nil)
			req.Header.Set("Authorization", "Bearer "+token)
			rec := httptest.NewRecorder()

			setup.Server.Config.Handler.ServeHTTP(rec, req)
			assert.Equal(t, tc.expectedStatus, rec.Code, tc.description)

			// For successful requests, verify response structure
			if tc.expectedStatus == http.StatusOK {
				var response map[string]interface{}
				err := json.NewDecoder(rec.Body).Decode(&response)
				require.NoError(t, err)

				// Verify all counts are zero for nonexistent library
				assert.Equal(t, float64(0), response["error_count"])
				assert.Equal(t, float64(0), response["warning_count"])
				assert.Equal(t, float64(0), response["info_count"])
			}
		})
	}
}

// TestProcessingIssuesCrossLibraryIsolation tests that different libraries don't interfere
func TestProcessingIssuesCrossLibraryIsolation(t *testing.T) {
	setup := setupTestServer(t)

	// Create two different libraries with different types
	deviceSetup1 := setupDeviceTest(t)
	library1ID := deviceSetup1.CreateLibrary(t, "Isolation Test Library 1", "manga")

	deviceSetup2 := setupDeviceTest(t)
	library2ID := deviceSetup2.CreateLibrary(t, "Isolation Test Library 2", "comics")

	token := setup.Token

	// Both libraries should return empty arrays (no issues created)
	t.Run("Library 1 returns its own empty list", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+library1ID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)

		var response []map[string]interface{}
		err := json.NewDecoder(rec.Body).Decode(&response)
		require.NoError(t, err)
		assert.Equal(t, 0, len(response), "Library 1 should have 0 issues")
	})

	t.Run("Library 2 returns its own empty list", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+library2ID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer "+token)
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusOK, rec.Code)

		var response []map[string]interface{}
		err := json.NewDecoder(rec.Body).Decode(&response)
		require.NoError(t, err)
		assert.Equal(t, 0, len(response), "Library 2 should have 0 issues")
	})

	t.Run("Both libraries have independent stats", func(t *testing.T) {
		// Get stats for library 1
		req1 := httptest.NewRequest("GET", "/api/libraries/"+library1ID+"/issues/stats", nil)
		req1.Header.Set("Authorization", "Bearer "+token)
		rec1 := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec1, req1)

		var stats1 map[string]interface{}
		err := json.NewDecoder(rec1.Body).Decode(&stats1)
		require.NoError(t, err)

		// Get stats for library 2
		req2 := httptest.NewRequest("GET", "/api/libraries/"+library2ID+"/issues/stats", nil)
		req2.Header.Set("Authorization", "Bearer "+token)
		rec2 := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec2, req2)

		var stats2 map[string]interface{}
		err = json.NewDecoder(rec2.Body).Decode(&stats2)
		require.NoError(t, err)

		// Both should have zero counts
		assert.Equal(t, float64(0), stats1["error_count"])
		assert.Equal(t, float64(0), stats1["warning_count"])
		assert.Equal(t, float64(0), stats1["info_count"])

		assert.Equal(t, float64(0), stats2["error_count"])
		assert.Equal(t, float64(0), stats2["warning_count"])
		assert.Equal(t, float64(0), stats2["info_count"])
	})
}

// TestProcessingIssuesDifferentLibraryTypes tests endpoints with different library types
func TestProcessingIssuesDifferentLibraryTypes(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	libraryTypes := []struct {
		name        string
		libraryType string
	}{
		{"Ebooks library", "ebooks"},
		{"Comics library", "comics"},
		{"Manga library", "manga"},
	}

	for _, lt := range libraryTypes {
		t.Run(lt.name+" - list endpoint works", func(t *testing.T) {
			deviceSetup := setupDeviceTest(t)
			libraryID := deviceSetup.CreateLibrary(t, "Test "+lt.name, lt.libraryType)

			req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
			req.Header.Set("Authorization", "Bearer "+token)
			rec := httptest.NewRecorder()

			setup.Server.Config.Handler.ServeHTTP(rec, req)
			assert.Equal(t, http.StatusOK, rec.Code)

			var response []map[string]interface{}
			err := json.NewDecoder(rec.Body).Decode(&response)
			require.NoError(t, err)
			assert.IsType(t, []map[string]interface{}{}, response)
		})

		t.Run(lt.name+" - stats endpoint works", func(t *testing.T) {
			deviceSetup := setupDeviceTest(t)
			libraryID := deviceSetup.CreateLibrary(t, "Test "+lt.name, lt.libraryType)

			req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
			req.Header.Set("Authorization", "Bearer "+token)
			rec := httptest.NewRecorder()

			setup.Server.Config.Handler.ServeHTTP(rec, req)
			assert.Equal(t, http.StatusOK, rec.Code)

			var response map[string]interface{}
			err := json.NewDecoder(rec.Body).Decode(&response)
			require.NoError(t, err)

			assert.Contains(t, response, "error_count")
			assert.Contains(t, response, "warning_count")
			assert.Contains(t, response, "info_count")
		})
	}
}

// TestProcessingIssuesInvalidTokens tests with invalid authentication tokens
func TestProcessingIssuesInvalidTokens(t *testing.T) {
	setup := setupTestServer(t)

	// Create a test library
	deviceSetup := setupDeviceTest(t)
	libraryID := deviceSetup.CreateLibrary(t, "Test Token Library", "ebooks")

	t.Run("List endpoint rejects invalid token", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "Bearer invalid-token-12345")
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("Stats endpoint rejects invalid token", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", "Bearer invalid-token-12345")
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("List endpoint rejects missing bearer prefix", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", setup.Token) // Missing "Bearer " prefix
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("Stats endpoint rejects missing bearer prefix", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/stats", nil)
		req.Header.Set("Authorization", setup.Token) // Missing "Bearer " prefix
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})

	t.Run("List endpoint rejects empty authorization header", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/libraries/"+libraryID+"/issues/list", nil)
		req.Header.Set("Authorization", "")
		rec := httptest.NewRecorder()

		setup.Server.Config.Handler.ServeHTTP(rec, req)
		assert.Equal(t, http.StatusUnauthorized, rec.Code)
	})
}
