package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/breakpilot/edu-search-service/internal/orchestrator"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// MockAudienceRepository implements orchestrator.AudienceRepository for testing
type MockAudienceRepository struct {
	audiences []orchestrator.Audience
	exports   []orchestrator.AudienceExport
	members   []orchestrator.AudienceMember
}

func NewMockAudienceRepository() *MockAudienceRepository {
	return &MockAudienceRepository{
		audiences: make([]orchestrator.Audience, 0),
		exports:   make([]orchestrator.AudienceExport, 0),
		members:   make([]orchestrator.AudienceMember, 0),
	}
}

func (m *MockAudienceRepository) CreateAudience(ctx context.Context, audience *orchestrator.Audience) error {
	audience.ID = uuid.New()
	audience.CreatedAt = time.Now()
	audience.UpdatedAt = time.Now()
	m.audiences = append(m.audiences, *audience)
	return nil
}

func (m *MockAudienceRepository) GetAudience(ctx context.Context, id uuid.UUID) (*orchestrator.Audience, error) {
	for i := range m.audiences {
		if m.audiences[i].ID == id {
			return &m.audiences[i], nil
		}
	}
	return nil, context.DeadlineExceeded // simulate not found
}

func (m *MockAudienceRepository) ListAudiences(ctx context.Context, activeOnly bool) ([]orchestrator.Audience, error) {
	if activeOnly {
		var active []orchestrator.Audience
		for _, a := range m.audiences {
			if a.IsActive {
				active = append(active, a)
			}
		}
		return active, nil
	}
	return m.audiences, nil
}

func (m *MockAudienceRepository) UpdateAudience(ctx context.Context, audience *orchestrator.Audience) error {
	for i := range m.audiences {
		if m.audiences[i].ID == audience.ID {
			m.audiences[i].Name = audience.Name
			m.audiences[i].Description = audience.Description
			m.audiences[i].Filters = audience.Filters
			m.audiences[i].IsActive = audience.IsActive
			m.audiences[i].UpdatedAt = time.Now()
			audience.UpdatedAt = m.audiences[i].UpdatedAt
			return nil
		}
	}
	return nil
}

func (m *MockAudienceRepository) DeleteAudience(ctx context.Context, id uuid.UUID) error {
	for i := range m.audiences {
		if m.audiences[i].ID == id {
			m.audiences[i].IsActive = false
			return nil
		}
	}
	return nil
}

func (m *MockAudienceRepository) GetAudienceMembers(ctx context.Context, id uuid.UUID, limit, offset int) ([]orchestrator.AudienceMember, int, error) {
	// Return mock members
	if len(m.members) == 0 {
		m.members = []orchestrator.AudienceMember{
			{
				ID:               uuid.New(),
				Name:             "Prof. Dr. Test Person",
				Email:            "test@university.de",
				Position:         "professor",
				University:       "Test Universität",
				Department:       "Informatik",
				SubjectArea:      "Informatik",
				PublicationCount: 42,
			},
			{
				ID:               uuid.New(),
				Name:             "Dr. Another Person",
				Email:            "another@university.de",
				Position:         "researcher",
				University:       "Test Universität",
				Department:       "Mathematik",
				SubjectArea:      "Mathematik",
				PublicationCount: 15,
			},
		}
	}

	total := len(m.members)
	if offset >= total {
		return []orchestrator.AudienceMember{}, total, nil
	}

	end := offset + limit
	if end > total {
		end = total
	}

	return m.members[offset:end], total, nil
}

func (m *MockAudienceRepository) UpdateAudienceCount(ctx context.Context, id uuid.UUID) (int, error) {
	count := len(m.members)
	for i := range m.audiences {
		if m.audiences[i].ID == id {
			m.audiences[i].MemberCount = count
			now := time.Now()
			m.audiences[i].LastCountUpdate = &now
		}
	}
	return count, nil
}

func (m *MockAudienceRepository) CreateExport(ctx context.Context, export *orchestrator.AudienceExport) error {
	export.ID = uuid.New()
	export.CreatedAt = time.Now()
	m.exports = append(m.exports, *export)
	return nil
}

func (m *MockAudienceRepository) ListExports(ctx context.Context, audienceID uuid.UUID) ([]orchestrator.AudienceExport, error) {
	var exports []orchestrator.AudienceExport
	for _, e := range m.exports {
		if e.AudienceID == audienceID {
			exports = append(exports, e)
		}
	}
	return exports, nil
}

func setupAudienceRouter(repo *MockAudienceRepository) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAudienceHandler(repo)

	v1 := router.Group("/v1")
	SetupAudienceRoutes(v1, handler)

	return router
}

func TestAudienceHandler_ListAudiences_Empty(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		Audiences []orchestrator.Audience `json:"audiences"`
		Count     int                     `json:"count"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.Count != 0 {
		t.Errorf("Expected 0 audiences, got %d", response.Count)
	}
}

func TestAudienceHandler_CreateAudience(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	body := CreateAudienceRequest{
		Name:        "Test Audience",
		Description: "A test audience for professors",
		Filters: orchestrator.AudienceFilters{
			PositionTypes: []string{"professor"},
			States:        []string{"BW", "BY"},
		},
		CreatedBy: "test-admin",
	}

	bodyJSON, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/v1/audiences", bytes.NewBuffer(bodyJSON))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

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

	if response.Name != "Test Audience" {
		t.Errorf("Expected name 'Test Audience', got '%s'", response.Name)
	}

	if !response.IsActive {
		t.Errorf("Expected audience to be active")
	}

	if len(repo.audiences) != 1 {
		t.Errorf("Expected 1 audience in repo, got %d", len(repo.audiences))
	}
}

func TestAudienceHandler_CreateAudience_InvalidJSON(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	req := httptest.NewRequest(http.MethodPost, "/v1/audiences", bytes.NewBuffer([]byte("invalid json")))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestAudienceHandler_CreateAudience_MissingName(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	body := map[string]interface{}{
		"description": "Missing name field",
	}

	bodyJSON, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/v1/audiences", bytes.NewBuffer(bodyJSON))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestAudienceHandler_GetAudience(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	// Create an audience first
	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		Description: "Test description",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/"+audience.ID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

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

	if response.Name != "Test Audience" {
		t.Errorf("Expected name 'Test Audience', got '%s'", response.Name)
	}
}

func TestAudienceHandler_GetAudience_InvalidID(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/invalid-uuid", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestAudienceHandler_GetAudience_NotFound(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/"+uuid.New().String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestAudienceHandler_UpdateAudience(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	// Create an audience first
	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Old Name",
		Description: "Old description",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	body := UpdateAudienceRequest{
		Name:        "New Name",
		Description: "New description",
		IsActive:    true,
	}

	bodyJSON, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPut, "/v1/audiences/"+audience.ID.String(), bytes.NewBuffer(bodyJSON))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Verify the update
	if repo.audiences[0].Name != "New Name" {
		t.Errorf("Expected name 'New Name', got '%s'", repo.audiences[0].Name)
	}
}

func TestAudienceHandler_DeleteAudience(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	// Create an audience first
	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "To Delete",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	req := httptest.NewRequest(http.MethodDelete, "/v1/audiences/"+audience.ID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Verify soft delete
	if repo.audiences[0].IsActive {
		t.Errorf("Expected audience to be inactive after delete")
	}
}

func TestAudienceHandler_GetAudienceMembers(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	// Create an audience first
	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/"+audience.ID.String()+"/members", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		Members    []orchestrator.AudienceMember `json:"members"`
		Count      int                           `json:"count"`
		TotalCount int                           `json:"total_count"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.TotalCount != 2 {
		t.Errorf("Expected 2 total members, got %d", response.TotalCount)
	}
}

func TestAudienceHandler_GetAudienceMembers_WithPagination(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/"+audience.ID.String()+"/members?limit=1&offset=0", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		Members []orchestrator.AudienceMember `json:"members"`
		Count   int                           `json:"count"`
		Limit   int                           `json:"limit"`
		Offset  int                           `json:"offset"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.Count != 1 {
		t.Errorf("Expected 1 member in response, got %d", response.Count)
	}

	if response.Limit != 1 {
		t.Errorf("Expected limit 1, got %d", response.Limit)
	}
}

func TestAudienceHandler_RefreshAudienceCount(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		IsActive:    true,
		MemberCount: 0,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	// Pre-initialize members so count works correctly
	repo.members = []orchestrator.AudienceMember{
		{ID: uuid.New(), Name: "Test Person 1"},
		{ID: uuid.New(), Name: "Test Person 2"},
	}

	req := httptest.NewRequest(http.MethodPost, "/v1/audiences/"+audience.ID.String()+"/refresh", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		AudienceID  string `json:"audience_id"`
		MemberCount int    `json:"member_count"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.MemberCount != 2 {
		t.Errorf("Expected member_count 2, got %d", response.MemberCount)
	}
}

func TestAudienceHandler_CreateExport(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	body := CreateExportRequest{
		ExportType: "csv",
		Purpose:    "Newsletter December 2024",
		ExportedBy: "admin",
	}

	bodyJSON, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/v1/audiences/"+audience.ID.String()+"/exports", bytes.NewBuffer(bodyJSON))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

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

	if response.ExportType != "csv" {
		t.Errorf("Expected export_type 'csv', got '%s'", response.ExportType)
	}

	if response.RecordCount != 2 {
		t.Errorf("Expected record_count 2, got %d", response.RecordCount)
	}
}

func TestAudienceHandler_ListExports(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	audience := orchestrator.Audience{
		ID:          uuid.New(),
		Name:        "Test Audience",
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	repo.audiences = append(repo.audiences, audience)

	// Add an export
	export := orchestrator.AudienceExport{
		ID:          uuid.New(),
		AudienceID:  audience.ID,
		ExportType:  "csv",
		RecordCount: 100,
		Purpose:     "Test export",
		CreatedAt:   time.Now(),
	}
	repo.exports = append(repo.exports, export)

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences/"+audience.ID.String()+"/exports", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		Exports []orchestrator.AudienceExport `json:"exports"`
		Count   int                           `json:"count"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.Count != 1 {
		t.Errorf("Expected 1 export, got %d", response.Count)
	}
}

func TestAudienceHandler_ListAudiences_ActiveOnly(t *testing.T) {
	repo := NewMockAudienceRepository()
	router := setupAudienceRouter(repo)

	// Add active and inactive audiences
	repo.audiences = []orchestrator.Audience{
		{ID: uuid.New(), Name: "Active", IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now()},
		{ID: uuid.New(), Name: "Inactive", IsActive: false, CreatedAt: time.Now(), UpdatedAt: time.Now()},
	}

	req := httptest.NewRequest(http.MethodGet, "/v1/audiences?active_only=true", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response struct {
		Audiences []orchestrator.Audience `json:"audiences"`
		Count     int                     `json:"count"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to unmarshal response: %v", err)
	}

	if response.Count != 1 {
		t.Errorf("Expected 1 active audience, got %d", response.Count)
	}

	if response.Audiences[0].Name != "Active" {
		t.Errorf("Expected audience 'Active', got '%s'", response.Audiences[0].Name)
	}
}
