package ai

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

	"github.com/your-username/todo/src/config"
)

// MockAnalyzer implements Analyzer interface for testing
type MockAnalyzer struct {
	result *AnalysisResult
	err    error
}

func (m *MockAnalyzer) AnalyzeTask(_ context.Context, _ string) (*AnalysisResult, error) {
	return m.result, m.err
}

func TestAIService(t *testing.T) {
	mockResult := &AnalysisResult{
		Priority:     3,
		Category:     "test",
		TimeEstimate: 30,
		Summary:      "Test task",
	}

	t.Run("SingleModel", func(t *testing.T) {
		service := NewAIService([]ModelConfig{{Name: "mock", Provider: "mock"}})
		service.analyzers = map[string]Analyzer{
			"mock": &MockAnalyzer{result: mockResult},
		}

		result, err := service.AnalyzeTask(context.Background(), "Test task")
		if err != nil {
			t.Errorf("Expected no error, got %v", err)
		}
		if result == nil {
			t.Fatal("Expected analysis result")
		}
		if result.Priority != mockResult.Priority {
			t.Errorf("Expected priority %d, got %d", mockResult.Priority, result.Priority)
		}
	})

	t.Run("Fallback", func(t *testing.T) {
		service := NewAIService([]ModelConfig{{Name: "failing", Provider: "mock"}})
		service.analyzers = map[string]Analyzer{
			"failing": &MockAnalyzer{err: fmt.Errorf("mock error")},
		}

		result, err := service.AnalyzeTask(context.Background(), "Test task")
		if err != nil {
			t.Errorf("Expected no error with fallback, got %v", err)
		}
		if result == nil {
			t.Fatal("Expected fallback analysis result")
		}
		if result.Category != "uncategorized" {
			t.Errorf("Expected uncategorized category, got %s", result.Category)
		}
	})

	t.Run("MultipleModels", func(t *testing.T) {
		service := NewAIService([]ModelConfig{
			{Name: "failing1", Provider: "mock"},
			{Name: "working", Provider: "mock"},
		})
		service.analyzers = map[string]Analyzer{
			"failing1": &MockAnalyzer{err: fmt.Errorf("mock error")},
			"working":  &MockAnalyzer{result: mockResult},
		}

		result, err := service.AnalyzeTask(context.Background(), "Test task")
		if err != nil {
			t.Errorf("Expected no error, got %v", err)
		}
		if result == nil {
			t.Fatal("Expected analysis result from second model")
		}
		if result.Priority != mockResult.Priority {
			t.Errorf("Expected priority %d, got %d", mockResult.Priority, result.Priority)
		}
	})
}

func TestAIService_AnalyzeTask(t *testing.T) {
	// Mock Ollama server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/generate" {
			t.Errorf("Expected to request '/api/generate', got: %s", r.URL.Path)
		}
		if r.Method != "POST" {
			t.Errorf("Expected POST request, got: %s", r.Method)
		}

		response := ollamaResponse{
			Response: `{
				"priority": 2,
				"category": "work",
				"time_estimate": 60,
				"related_tasks": "Follow up with team",
				"summary": "Project status report for Q2"
			}`,
		}
		json.NewEncoder(w).Encode(response)
	}))
	defer server.Close()

	// Create test config
	cfg := &config.AIConfig{
		OllamaBaseURL: server.URL,
		MaxRetries:    2,
		RetryDelay:    100,
		Timeout:       5,
		MaxTokens:     2048,
		Models: []config.AIModelConfig{
			{
				Name:     "test-model",
				Provider: "ollama",
			},
		},
	}

	service := NewAIService(cfg)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := service.AnalyzeTask(ctx, "Create project status report")
	if err != nil {
		t.Fatalf("Failed to analyze task: %v", err)
	}

	if result.Priority != 2 {
		t.Errorf("Expected priority 2, got %d", result.Priority)
	}
	if result.Category != "work" {
		t.Errorf("Expected category 'work', got %s", result.Category)
	}
	if result.TimeEstimate != 60 {
		t.Errorf("Expected time estimate 60, got %d", result.TimeEstimate)
	}
}

func TestAIService_Fallback(t *testing.T) {
	// Create config with invalid URL to force fallback
	cfg := &config.AIConfig{
		OllamaBaseURL: "http://invalid-url",
		MaxRetries:    1,
		RetryDelay:    100,
		Timeout:       1,
		Models: []config.AIModelConfig{
			{
				Name:     "test-model",
				Provider: "ollama",
			},
		},
	}

	service := NewAIService(cfg)

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	result, err := service.AnalyzeTask(ctx, "Buy groceries")
	if err == nil {
		t.Error("Expected error due to invalid URL, got nil")
	}

	// Should still get a result from fallback
	if result == nil {
		t.Fatal("Expected fallback result, got nil")
	}
	if result.Category != "shopping" {
		t.Errorf("Expected category 'shopping', got %s", result.Category)
	}
}

func TestInferCategory(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"Education", "Learn Python programming", "education"},
		{"Work", "Complete project report", "work"},
		{"Shopping", "Buy groceries", "shopping"},
		{"Communication", "Call mom", "communication"},
		{"Maintenance", "Fix the leaky faucet", "maintenance"},
		{"Health", "Go to the gym", "health"},
		{"Default", "Random task", "general"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := inferCategory(tt.input)
			if result != tt.expected {
				t.Errorf("inferCategory(%q) = %q; want %q", tt.input, result, tt.expected)
			}
		})
	}
}

func TestAnalysisResult_ToJSON(t *testing.T) {
	result := &AnalysisResult{
		Priority:     1,
		Category:     "work",
		TimeEstimate: 30,
		RelatedTasks: "Follow up",
		Summary:      "Test task",
	}

	json, err := result.ToJSON()
	if err != nil {
		t.Fatalf("Failed to convert to JSON: %v", err)
	}

	expected := `{"priority":1,"category":"work","time_estimate":30,"related_tasks":"Follow up","summary":"Test task"}`
	if json != expected {
		t.Errorf("Expected JSON %q, got %q", expected, json)
	}
}

func TestOllamaAnalyzer_ValidateResult(t *testing.T) {
	analyzer := &OllamaAnalyzer{
		model: "test-model",
	}

	tests := []struct {
		name         string
		input        *AnalysisResult
		taskTitle    string
		wantPriority int
		wantEstimate int
	}{
		{
			name: "Valid result",
			input: &AnalysisResult{
				Priority:     2,
				TimeEstimate: 45,
				Category:     "work",
				Summary:      "Valid task",
			},
			taskTitle:    "Test task",
			wantPriority: 2,
			wantEstimate: 45,
		},
		{
			name: "Invalid priority",
			input: &AnalysisResult{
				Priority:     0,
				TimeEstimate: 45,
				Category:     "work",
			},
			taskTitle:    "Test task",
			wantPriority: 3,
			wantEstimate: 45,
		},
		{
			name: "Invalid estimate",
			input: &AnalysisResult{
				Priority:     2,
				TimeEstimate: 0,
				Category:     "work",
			},
			taskTitle:    "Test task",
			wantPriority: 2,
			wantEstimate: 30,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := analyzer.validateResult(tt.input, tt.taskTitle)
			if result.Priority != tt.wantPriority {
				t.Errorf("Priority = %d; want %d", result.Priority, tt.wantPriority)
			}
			if result.TimeEstimate != tt.wantEstimate {
				t.Errorf("TimeEstimate = %d; want %d", result.TimeEstimate, tt.wantEstimate)
			}
			if result.Summary == "" {
				t.Error("Summary should not be empty")
			}
			if result.Category == "" {
				t.Error("Category should not be empty")
			}
		})
	}
}
