package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
)

func init() {
	gin.SetMode(gin.TestMode)
}

// setupTestRouter creates a test router with the handler
func setupTestRouter(h *Handler, apiKey string) *gin.Engine {
	router := gin.New()
	SetupRoutes(router, h, apiKey)
	return router
}

// setupTestSeedStore creates a test seed store
func setupTestSeedStore(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()

	// Initialize global seed store
	err := InitSeedStore(dir)
	if err != nil {
		t.Fatalf("Failed to initialize seed store: %v", err)
	}

	return dir
}

func TestHealthEndpoint(t *testing.T) {
	// Health endpoint requires indexClient for health check
	// This test verifies the route is set up correctly
	// A full integration test would need a mock OpenSearch client
	t.Skip("Skipping: requires mock indexer client for full test")
}

func TestAuthMiddleware_NoAuth(t *testing.T) {
	h := &Handler{}
	router := setupTestRouter(h, "test-api-key")

	// Request without auth header
	req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", w.Code)
	}
}

func TestAuthMiddleware_InvalidFormat(t *testing.T) {
	h := &Handler{}
	router := setupTestRouter(h, "test-api-key")

	// Request with wrong auth format
	req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Basic dGVzdDp0ZXN0") // Basic auth instead of Bearer
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", w.Code)
	}
}

func TestAuthMiddleware_InvalidKey(t *testing.T) {
	h := &Handler{}
	router := setupTestRouter(h, "test-api-key")

	// Request with wrong API key
	req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer wrong-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", w.Code)
	}
}

func TestAuthMiddleware_ValidKey(t *testing.T) {
	h := &Handler{}
	router := setupTestRouter(h, "test-api-key")

	// Request with correct API key (search will fail due to no search service, but auth should pass)
	req, _ := http.NewRequest("GET", "/v1/document?doc_id=test", nil)
	req.Header.Set("Authorization", "Bearer test-api-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Auth should pass, endpoint returns 501 (not implemented)
	if w.Code == http.StatusUnauthorized {
		t.Error("Expected auth to pass, got 401")
	}
}

func TestAuthMiddleware_HealthNoAuth(t *testing.T) {
	// Health endpoint requires indexClient for health check
	// Skipping because route calls h.indexClient.Health() which panics with nil
	t.Skip("Skipping: requires mock indexer client for full test")
}

func TestGetDocument_MissingDocID(t *testing.T) {
	h := &Handler{}
	router := setupTestRouter(h, "test-key")

	req, _ := http.NewRequest("GET", "/v1/document", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("Expected status 400, got %d", w.Code)
	}
}

// Admin Handler Tests

func TestSeedStore_InitAndLoad(t *testing.T) {
	dir := t.TempDir()

	// First initialization should create default seeds
	err := InitSeedStore(dir)
	if err != nil {
		t.Fatalf("InitSeedStore failed: %v", err)
	}

	// Check that seeds file was created
	seedsFile := filepath.Join(dir, "seeds.json")
	if _, err := os.Stat(seedsFile); os.IsNotExist(err) {
		t.Error("seeds.json was not created")
	}

	// Check that default seeds were loaded
	seeds := seedStore.GetAllSeeds()
	if len(seeds) == 0 {
		t.Error("Expected default seeds to be loaded")
	}
}

func TestSeedStore_CreateSeed(t *testing.T) {
	setupTestSeedStore(t)

	newSeed := SeedURL{
		URL:         "https://test.example.com",
		Name:        "Test Seed",
		Category:    "test",
		Description: "A test seed",
		TrustBoost:  0.5,
		Enabled:     true,
	}

	created, err := seedStore.CreateSeed(newSeed)
	if err != nil {
		t.Fatalf("CreateSeed failed: %v", err)
	}

	if created.ID == "" {
		t.Error("Expected generated ID")
	}
	if created.URL != newSeed.URL {
		t.Errorf("Expected URL %q, got %q", newSeed.URL, created.URL)
	}
	if created.CreatedAt.IsZero() {
		t.Error("Expected CreatedAt to be set")
	}
}

func TestSeedStore_GetSeed(t *testing.T) {
	setupTestSeedStore(t)

	// Create a seed first
	newSeed := SeedURL{
		URL:      "https://get-test.example.com",
		Name:     "Get Test",
		Category: "test",
	}
	created, _ := seedStore.CreateSeed(newSeed)

	// Get the seed
	retrieved, found := seedStore.GetSeed(created.ID)
	if !found {
		t.Fatal("Seed not found")
	}

	if retrieved.URL != newSeed.URL {
		t.Errorf("Expected URL %q, got %q", newSeed.URL, retrieved.URL)
	}
}

func TestSeedStore_GetSeed_NotFound(t *testing.T) {
	setupTestSeedStore(t)

	_, found := seedStore.GetSeed("nonexistent-id")
	if found {
		t.Error("Expected seed not to be found")
	}
}

func TestSeedStore_UpdateSeed(t *testing.T) {
	setupTestSeedStore(t)

	// Create a seed first
	original := SeedURL{
		URL:      "https://update-test.example.com",
		Name:     "Original Name",
		Category: "test",
		Enabled:  true,
	}
	created, _ := seedStore.CreateSeed(original)

	// Update the seed
	updates := SeedURL{
		Name:       "Updated Name",
		TrustBoost: 0.75,
		Enabled:    false,
	}

	updated, found, err := seedStore.UpdateSeed(created.ID, updates)
	if err != nil {
		t.Fatalf("UpdateSeed failed: %v", err)
	}
	if !found {
		t.Fatal("Seed not found for update")
	}

	if updated.Name != "Updated Name" {
		t.Errorf("Expected name 'Updated Name', got %q", updated.Name)
	}
	if updated.TrustBoost != 0.75 {
		t.Errorf("Expected TrustBoost 0.75, got %f", updated.TrustBoost)
	}
	if updated.Enabled != false {
		t.Error("Expected Enabled to be false")
	}
	// URL should remain unchanged since we didn't provide it
	if updated.URL != original.URL {
		t.Errorf("URL should remain unchanged, expected %q, got %q", original.URL, updated.URL)
	}
}

func TestSeedStore_UpdateSeed_NotFound(t *testing.T) {
	setupTestSeedStore(t)

	updates := SeedURL{Name: "New Name"}
	_, found, err := seedStore.UpdateSeed("nonexistent-id", updates)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if found {
		t.Error("Expected seed not to be found")
	}
}

func TestSeedStore_DeleteSeed(t *testing.T) {
	setupTestSeedStore(t)

	// Create a seed first
	newSeed := SeedURL{
		URL:      "https://delete-test.example.com",
		Name:     "Delete Test",
		Category: "test",
	}
	created, _ := seedStore.CreateSeed(newSeed)

	// Delete the seed
	deleted := seedStore.DeleteSeed(created.ID)
	if !deleted {
		t.Error("Expected delete to succeed")
	}

	// Verify it's gone
	_, found := seedStore.GetSeed(created.ID)
	if found {
		t.Error("Seed should have been deleted")
	}
}

func TestSeedStore_DeleteSeed_NotFound(t *testing.T) {
	setupTestSeedStore(t)

	deleted := seedStore.DeleteSeed("nonexistent-id")
	if deleted {
		t.Error("Expected delete to return false for nonexistent seed")
	}
}

func TestSeedStore_Persistence(t *testing.T) {
	dir := t.TempDir()

	// Create and populate seed store
	err := InitSeedStore(dir)
	if err != nil {
		t.Fatal(err)
	}

	newSeed := SeedURL{
		URL:      "https://persist-test.example.com",
		Name:     "Persistence Test",
		Category: "test",
	}
	created, err := seedStore.CreateSeed(newSeed)
	if err != nil {
		t.Fatal(err)
	}

	// Re-initialize from the same directory
	seedStore = nil
	err = InitSeedStore(dir)
	if err != nil {
		t.Fatal(err)
	}

	// Check if the seed persisted
	retrieved, found := seedStore.GetSeed(created.ID)
	if !found {
		t.Error("Seed should have persisted")
	}
	if retrieved.URL != newSeed.URL {
		t.Errorf("Persisted seed URL mismatch: expected %q, got %q", newSeed.URL, retrieved.URL)
	}
}

func TestAdminGetSeeds(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")

	// Initialize seed store for the test
	InitSeedStore(dir)

	req, _ := http.NewRequest("GET", "/v1/admin/seeds", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	var seeds []SeedURL
	if err := json.Unmarshal(w.Body.Bytes(), &seeds); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	// Should have default seeds
	if len(seeds) == 0 {
		t.Error("Expected seeds to be returned")
	}
}

func TestAdminCreateSeed(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	newSeed := map[string]interface{}{
		"url":         "https://new-seed.example.com",
		"name":        "New Seed",
		"category":    "test",
		"description": "Test description",
		"trustBoost":  0.5,
		"enabled":     true,
	}

	body, _ := json.Marshal(newSeed)
	req, _ := http.NewRequest("POST", "/v1/admin/seeds", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Errorf("Expected status 201, got %d: %s", w.Code, w.Body.String())
	}

	var created SeedURL
	if err := json.Unmarshal(w.Body.Bytes(), &created); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if created.ID == "" {
		t.Error("Expected ID to be generated")
	}
	if created.URL != "https://new-seed.example.com" {
		t.Errorf("Expected URL to match, got %q", created.URL)
	}
}

func TestAdminCreateSeed_MissingURL(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	newSeed := map[string]interface{}{
		"name":     "No URL Seed",
		"category": "test",
	}

	body, _ := json.Marshal(newSeed)
	req, _ := http.NewRequest("POST", "/v1/admin/seeds", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("Expected status 400 for missing URL, got %d", w.Code)
	}
}

func TestAdminUpdateSeed(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	// Create a seed first
	newSeed := SeedURL{
		URL:      "https://update-api-test.example.com",
		Name:     "API Update Test",
		Category: "test",
	}
	created, _ := seedStore.CreateSeed(newSeed)

	// Update via API
	updates := map[string]interface{}{
		"name":       "Updated via API",
		"trustBoost": 0.8,
	}

	body, _ := json.Marshal(updates)
	req, _ := http.NewRequest("PUT", "/v1/admin/seeds/"+created.ID, bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	var updated SeedURL
	if err := json.Unmarshal(w.Body.Bytes(), &updated); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if updated.Name != "Updated via API" {
		t.Errorf("Expected name 'Updated via API', got %q", updated.Name)
	}
}

func TestAdminDeleteSeed(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	// Create a seed first
	newSeed := SeedURL{
		URL:      "https://delete-api-test.example.com",
		Name:     "API Delete Test",
		Category: "test",
	}
	created, _ := seedStore.CreateSeed(newSeed)

	// Delete via API
	req, _ := http.NewRequest("DELETE", "/v1/admin/seeds/"+created.ID, nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	// Verify it's deleted
	_, found := seedStore.GetSeed(created.ID)
	if found {
		t.Error("Seed should have been deleted")
	}
}

func TestAdminDeleteSeed_NotFound(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	req, _ := http.NewRequest("DELETE", "/v1/admin/seeds/nonexistent-id", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Errorf("Expected status 404, got %d", w.Code)
	}
}

func TestAdminGetStats(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	req, _ := http.NewRequest("GET", "/v1/admin/stats", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	var stats CrawlStats
	if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	// Check that stats structure is populated
	if stats.CrawlStatus == "" {
		t.Error("Expected CrawlStatus to be set")
	}
	if stats.DocumentsPerCategory == nil {
		t.Error("Expected DocumentsPerCategory to be set")
	}
}

func TestAdminStartCrawl(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	// Reset crawl status
	crawlStatus = "idle"

	req, _ := http.NewRequest("POST", "/v1/admin/crawl/start", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusAccepted {
		t.Errorf("Expected status 202, got %d: %s", w.Code, w.Body.String())
	}

	var response map[string]interface{}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if response["status"] != "started" {
		t.Errorf("Expected status 'started', got %v", response["status"])
	}
}

func TestAdminStartCrawl_AlreadyRunning(t *testing.T) {
	dir := setupTestSeedStore(t)

	h := &Handler{}
	router := gin.New()
	SetupRoutes(router, h, "test-key")
	InitSeedStore(dir)

	// Set crawl status to running
	crawlStatus = "running"

	req, _ := http.NewRequest("POST", "/v1/admin/crawl/start", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Code != http.StatusConflict {
		t.Errorf("Expected status 409, got %d", w.Code)
	}

	// Reset for other tests
	crawlStatus = "idle"
}

func TestConcurrentSeedAccess(t *testing.T) {
	setupTestSeedStore(t)

	// Test concurrent reads and writes
	done := make(chan bool, 10)

	// Concurrent readers
	for i := 0; i < 5; i++ {
		go func() {
			seedStore.GetAllSeeds()
			done <- true
		}()
	}

	// Concurrent writers
	for i := 0; i < 5; i++ {
		go func(n int) {
			seed := SeedURL{
				URL:      "https://concurrent-" + string(rune('A'+n)) + ".example.com",
				Name:     "Concurrent Test",
				Category: "test",
			}
			seedStore.CreateSeed(seed)
			done <- true
		}(i)
	}

	// Wait for all goroutines
	for i := 0; i < 10; i++ {
		<-done
	}

	// If we get here without deadlock or race, test passes
}
