package ui

import (
	"context"
	"database/sql"
	"testing"
	"time"

	tea "github.com/charmbracelet/bubbletea"

	"github.com/your-username/todo/src/ai"
	"github.com/your-username/todo/src/database"
)

// MockDatabase implements Database interface for testing
type MockDatabase struct {
	tasks    []database.Task
	analyses map[int64]*database.Analysis
	lastID   int64
}

func NewMockDB() *MockDatabase {
	return &MockDatabase{
		tasks:    make([]database.Task, 0),
		analyses: make(map[int64]*database.Analysis),
		lastID:   0,
	}
}

func (m *MockDatabase) Reset() {
	m.tasks = make([]database.Task, 0)
	m.analyses = make(map[int64]*database.Analysis)
	m.lastID = 0
}

func (m *MockDatabase) AddTask(_ context.Context, title string, body string) (int64, error) {
	m.lastID++
	task := database.Task{
		ID:        m.lastID,
		Title:     title,
		Body:      body,
		CreatedAt: time.Now(),
	}
	m.tasks = append(m.tasks, task)
	return task.ID, nil
}

func (m *MockDatabase) GetTasks(_ context.Context, includeCompleted bool) ([]database.Task, error) {
	var result []database.Task
	for _, t := range m.tasks {
		if includeCompleted || !t.Completed {
			result = append(result, t)
		}
	}
	return result, nil
}

func (m *MockDatabase) CompleteTask(_ context.Context, id int) error {
	for i := range m.tasks {
		if m.tasks[i].ID == int64(id) {
			m.tasks[i].Completed = true
			return nil
		}
	}
	return sql.ErrNoRows
}

func (m *MockDatabase) UpdateTask(_ context.Context, id int64, title string, body string) error {
	for i := range m.tasks {
		if m.tasks[i].ID == id {
			m.tasks[i].Title = title
			m.tasks[i].Body = body
			return nil
		}
	}
	return sql.ErrNoRows
}

func (m *MockDatabase) GetTaskAnalysis(_ context.Context, taskID int) (*database.Analysis, error) {
	return m.analyses[int64(taskID)], nil
}

func (m *MockDatabase) StoreAnalysis(_ context.Context, analysis *database.Analysis) error {
	m.analyses[analysis.TaskID] = analysis
	return nil
}

// MockAIService for testing
type MockAIService struct {
	result *ai.AnalysisResult
}

func NewMockAIService() *ai.AIService {
	return ai.NewAIService([]ai.ModelConfig{
		{
			Name:     "mock",
			Provider: "mock",
		},
	})
}

type MockAnalyzer struct {
	result *ai.AnalysisResult
}

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

func TestTUI(t *testing.T) {
	mockDB := NewMockDB()
	mockAI := NewMockAIService()

	t.Run("InitialState", func(t *testing.T) {
		mockDB.Reset()
		model := New(mockDB, mockAI)
		if model.state != StateNormal {
			t.Errorf("Expected initial state to be StateNormal")
		}
	})

	t.Run("AddTask", func(t *testing.T) {
		mockDB.Reset()
		model := New(mockDB, mockAI)

		// Simulate pressing 'n' to enter creation mode
		updatedModel, cmd := model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("n")})
		model = updatedModel.(Model)

		if model.state != StateCreating {
			t.Error("Expected state to be StateCreating after pressing 'n'")
		}

		// Simulate typing task title
		model.titleInput.SetValue("Test task")

		// Simulate pressing enter to create task
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyEnter})
		model = updatedModel.(Model)

		// Execute any pending commands
		if cmd != nil {
			msg := cmd()
			updatedModel, _ = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify task was added
		tasks, _ := mockDB.GetTasks(context.Background(), false)
		if len(tasks) != 1 {
			t.Error("Expected one task to be added")
		}
		if len(tasks) > 0 && tasks[0].Title != "Test task" {
			t.Errorf("Expected task title 'Test task', got '%s'", tasks[0].Title)
		}
	})

	t.Run("CompleteTask", func(t *testing.T) {
		mockDB.Reset()
		model := New(mockDB, mockAI)

		// Add a task first
		id, _ := mockDB.AddTask(context.Background(), "Task to complete", "")

		// Reload task list and execute the command
		updatedModel, cmd := model.Update(nil)
		model = updatedModel.(Model)
		if cmd != nil {
			msg := cmd()
			updatedModel, cmd = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify task is in list
		items := model.list.Items()
		if len(items) != 1 {
			t.Errorf("Expected 1 task in list before completion, got %d", len(items))
		}

		// Select first task
		model.list.Select(0)

		// Verify correct task is selected
		if i, ok := model.list.SelectedItem().(taskItem); ok {
			if i.Task.ID != id {
				t.Errorf("Expected selected task ID %d, got %d", id, i.Task.ID)
			}
		} else {
			t.Error("Could not get selected task")
		}

		// Simulate pressing 'c' to complete
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("c")})
		model = updatedModel.(Model)

		// Execute any pending commands
		if cmd != nil {
			msg := cmd()
			updatedModel, _ = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify task was completed
		tasks, _ := mockDB.GetTasks(context.Background(), true)
		found := false
		for _, task := range tasks {
			if task.ID == id && task.Completed {
				found = true
				break
			}
		}
		if !found {
			t.Error("Expected task to be marked as completed")
		}
	})

	t.Run("SearchTasks", func(t *testing.T) {
		mockDB.Reset()
		model := New(mockDB, mockAI)

		// Add test tasks
		mockDB.AddTask(context.Background(), "Test task 1", "")
		mockDB.AddTask(context.Background(), "Test task 2", "")
		mockDB.AddTask(context.Background(), "Different task", "")

		// Reload task list and execute the command
		updatedModel, cmd := model.Update(nil)
		model = updatedModel.(Model)
		if cmd != nil {
			msg := cmd()
			updatedModel, cmd = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify tasks are in list
		items := model.list.Items()
		if len(items) != 3 {
			t.Errorf("Expected 3 tasks in list before search, got %d", len(items))
		}

		// Simulate pressing '/' to enter search mode
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")})
		model = updatedModel.(Model)

		if model.state != StateSearching {
			t.Error("Expected state to be StateSearching")
		}

		// Set search filter
		model.searchInput.SetValue("Test")
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyEnter})
		model = updatedModel.(Model)

		// Execute any pending commands
		if cmd != nil {
			msg := cmd()
			updatedModel, _ = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify filtered tasks
		items = model.list.Items()
		if len(items) != 2 {
			t.Errorf("Expected 2 filtered tasks, got %d", len(items))
			for _, item := range items {
				t.Logf("Task: %s", item.FilterValue())
			}
		}
	})

	t.Run("EditTask", func(t *testing.T) {
		mockDB.Reset()
		model := New(mockDB, mockAI)

		// Add a task first
		id, _ := mockDB.AddTask(context.Background(), "Task to edit", "Original description")

		// Reload task list and execute the command
		updatedModel, cmd := model.Update(nil)
		model = updatedModel.(Model)
		if cmd != nil {
			msg := cmd()
			updatedModel, cmd = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Select first task
		model.list.Select(0)

		// Verify correct task is selected
		if i, ok := model.list.SelectedItem().(taskItem); ok {
			if i.Task.ID != id {
				t.Errorf("Expected selected task ID %d, got %d", id, i.Task.ID)
			}
		} else {
			t.Error("Could not get selected task")
		}

		// Simulate pressing 'e' to edit
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("e")})
		model = updatedModel.(Model)

		if model.state != StateEditing {
			t.Error("Expected state to be StateEditing")
		}

		// Set new values
		model.titleInput.SetValue("Updated task")
		model.bodyInput.SetValue("Updated description")

		// Simulate pressing enter to move to body input
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyEnter})
		model = updatedModel.(Model)

		// Simulate pressing enter to save
		updatedModel, cmd = model.Update(tea.KeyMsg{Type: tea.KeyEnter})
		model = updatedModel.(Model)

		// Execute any pending commands
		if cmd != nil {
			msg := cmd()
			updatedModel, _ = model.Update(msg)
			model = updatedModel.(Model)
		}

		// Verify task was updated
		tasks, _ := mockDB.GetTasks(context.Background(), false)
		found := false
		for _, task := range tasks {
			if task.ID == id {
				if task.Title != "Updated task" || task.Body != "Updated description" {
					t.Errorf("Expected task to be updated with new title and description")
				}
				found = true
				break
			}
		}
		if !found {
			t.Error("Could not find updated task")
		}
	})
}
