package repositories

import (
	"testing"
	"time"

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

	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

// === UpdateTx / Version Conflict Tests ===

func TestTaskRepository_UpdateTx_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")

	// Simulate stale version by setting wrong version
	task.Version = 999
	task.Title = "Should Fail"

	tx := db.Begin()
	err := repo.UpdateTx(tx, task)
	tx.Rollback()

	assert.ErrorIs(t, err, ErrVersionConflict)
}

func TestTaskRepository_UpdateTx_Success(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")

	originalVersion := task.Version
	task.Title = "Updated via Tx"

	tx := db.Begin()
	err := repo.UpdateTx(tx, task)
	require.NoError(t, err)
	tx.Commit()

	assert.Equal(t, originalVersion+1, task.Version)

	found, err := repo.FindByID(task.ID)
	require.NoError(t, err)
	assert.Equal(t, "Updated via Tx", found.Title)
}

func TestTaskRepository_Update_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")

	task.Version = 999 // stale version
	task.Title = "Should Fail"
	err := repo.Update(task)
	assert.ErrorIs(t, err, ErrVersionConflict)
}

// === Version Conflict on State Operations ===

func TestTaskRepository_Cancel_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.Cancel(task.ID, 999) // wrong version
	assert.ErrorIs(t, err, ErrVersionConflict)
}

func TestTaskRepository_Uncancel_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.Uncancel(task.ID, 999)
	assert.ErrorIs(t, err, ErrVersionConflict)
}

func TestTaskRepository_Archive_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.Archive(task.ID, 999)
	assert.ErrorIs(t, err, ErrVersionConflict)
}

func TestTaskRepository_Unarchive_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.Unarchive(task.ID, 999)
	assert.ErrorIs(t, err, ErrVersionConflict)
}

func TestTaskRepository_MarkInProgress(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.MarkInProgress(task.ID, task.Version)
	require.NoError(t, err)

	found, err := repo.FindByID(task.ID)
	require.NoError(t, err)
	assert.True(t, found.InProgress)
}

func TestTaskRepository_MarkInProgress_VersionConflict(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	err := repo.MarkInProgress(task.ID, 999)
	assert.ErrorIs(t, err, ErrVersionConflict)
}

// === CreateCompletionTx ===

func TestTaskRepository_CreateCompletionTx(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	tx := db.Begin()
	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
		Notes:         "Done via tx",
	}
	err := repo.CreateCompletionTx(tx, completion)
	require.NoError(t, err)
	tx.Commit()

	assert.NotZero(t, completion.ID)

	found, err := repo.FindCompletionByID(completion.ID)
	require.NoError(t, err)
	assert.Equal(t, "Done via tx", found.Notes)
}

// === GetFrequencyByID ===

func TestTaskRepository_GetFrequencyByID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewTaskRepository(db)

	frequencies, err := repo.GetAllFrequencies()
	require.NoError(t, err)
	require.NotEmpty(t, frequencies)

	found, err := repo.GetFrequencyByID(frequencies[0].ID)
	require.NoError(t, err)
	assert.Equal(t, frequencies[0].Name, found.Name)
}

func TestTaskRepository_GetFrequencyByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	_, err := repo.GetFrequencyByID(9999)
	assert.Error(t, err)
}

// === FindByUser ===

func TestTaskRepository_FindByUser(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")

	testutil.CreateTestTask(t, db, r1.ID, user.ID, "Task A")
	testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task B")

	tasks, err := repo.FindByUser(user.ID, []uint{r1.ID, r2.ID})
	require.NoError(t, err)
	assert.Len(t, tasks, 2)
}

// === CountByResidenceIDs ===

func TestTaskRepository_CountByResidenceIDs(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")

	testutil.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
	testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")
	testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 3")

	count, err := repo.CountByResidenceIDs([]uint{r1.ID, r2.ID})
	require.NoError(t, err)
	assert.Equal(t, int64(3), count)
}

func TestTaskRepository_CountByResidenceIDs_Empty(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	count, err := repo.CountByResidenceIDs([]uint{})
	require.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

// === FindCompletionsByUser ===

func TestTaskRepository_FindCompletionsByUser(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	c := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
	}
	require.NoError(t, db.Create(c).Error)

	completions, err := repo.FindCompletionsByUser(user.ID, []uint{residence.ID})
	require.NoError(t, err)
	assert.Len(t, completions, 1)
}

// === UpdateCompletion ===

func TestTaskRepository_UpdateCompletion(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
		Notes:         "Original",
	}
	require.NoError(t, db.Create(completion).Error)

	completion.Notes = "Updated notes"
	err := repo.UpdateCompletion(completion)
	require.NoError(t, err)

	found, err := repo.FindCompletionByID(completion.ID)
	require.NoError(t, err)
	assert.Equal(t, "Updated notes", found.Notes)
}

// === CompletionImage CRUD ===

func TestTaskRepository_CompletionImage_CRUD(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
	}
	require.NoError(t, db.Create(completion).Error)

	// Create image
	img := &models.TaskCompletionImage{
		CompletionID: completion.ID,
		ImageURL:     "https://example.com/img.jpg",
	}
	err := repo.CreateCompletionImage(img)
	require.NoError(t, err)
	assert.NotZero(t, img.ID)

	// Find image
	found, err := repo.FindCompletionImageByID(img.ID)
	require.NoError(t, err)
	assert.Equal(t, "https://example.com/img.jpg", found.ImageURL)

	// Delete image
	err = repo.DeleteCompletionImage(img.ID)
	require.NoError(t, err)

	_, err = repo.FindCompletionImageByID(img.ID)
	assert.Error(t, err)
}

func TestTaskRepository_FindCompletionImageByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	_, err := repo.FindCompletionImageByID(9999)
	assert.Error(t, err)
}

// === GetOverdueCountByResidence ===

func TestTaskRepository_GetOverdueCountByResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")

	now := time.Now().UTC()
	pastDue := now.AddDate(0, 0, -5)

	// Overdue task in r1
	t1 := &models.Task{
		ResidenceID: r1.ID,
		CreatedByID: user.ID,
		Title:       "Overdue in r1",
		DueDate:     &pastDue,
		Version:     1,
	}
	require.NoError(t, db.Create(t1).Error)

	// Not overdue task in r2 (future)
	futureDue := now.AddDate(0, 0, 30)
	t2 := &models.Task{
		ResidenceID: r2.ID,
		CreatedByID: user.ID,
		Title:       "Future in r2",
		DueDate:     &futureDue,
		Version:     1,
	}
	require.NoError(t, db.Create(t2).Error)

	countMap, err := repo.GetOverdueCountByResidence([]uint{r1.ID, r2.ID}, now)
	require.NoError(t, err)
	assert.Equal(t, 1, countMap[r1.ID])
	assert.Equal(t, 0, countMap[r2.ID])
}

func TestTaskRepository_GetOverdueCountByResidence_EmptyIDs(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	countMap, err := repo.GetOverdueCountByResidence([]uint{}, time.Now().UTC())
	require.NoError(t, err)
	assert.Empty(t, countMap)
}

// === GetKanbanDataForMultipleResidences ===

func TestTaskRepository_GetKanbanDataForMultipleResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")

	testutil.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
	testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")

	board, err := repo.GetKanbanDataForMultipleResidences([]uint{r1.ID, r2.ID}, 30, time.Now().UTC())
	require.NoError(t, err)
	assert.Equal(t, "all", board.ResidenceID)
	assert.Len(t, board.Columns, 5)

	// Both tasks should appear in upcoming (no due date)
	var upcomingCol *models.KanbanColumn
	for i := range board.Columns {
		if board.Columns[i].Name == "upcoming_tasks" {
			upcomingCol = &board.Columns[i]
		}
	}
	require.NotNil(t, upcomingCol)
	assert.Equal(t, 2, upcomingCol.Count)
}

// === DB() accessor ===

func TestTaskRepository_DB(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	assert.NotNil(t, repo.DB())
}

// === GetBatchCompletionSummaries ===

func TestTaskRepository_GetBatchCompletionSummaries_Empty(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	result, err := repo.GetBatchCompletionSummaries([]uint{}, time.Now().UTC(), 10)
	require.NoError(t, err)
	assert.Empty(t, result)
}

func TestTaskRepository_GetBatchCompletionSummaries_WithData(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")
	task1 := testutil.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
	task2 := testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")

	now := time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)
	c1 := models.TaskCompletion{
		TaskID: task1.ID, CompletedByID: user.ID,
		CompletedAt: time.Date(2026, 2, 1, 12, 0, 0, 0, time.UTC), CompletedFromColumn: "completed_tasks",
	}
	require.NoError(t, db.Create(&c1).Error)

	c2 := models.TaskCompletion{
		TaskID: task2.ID, CompletedByID: user.ID,
		CompletedAt: time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC), CompletedFromColumn: "overdue_tasks",
	}
	require.NoError(t, db.Create(&c2).Error)

	result, err := repo.GetBatchCompletionSummaries([]uint{r1.ID, r2.ID}, now, 10)
	require.NoError(t, err)
	assert.Len(t, result, 2)

	assert.Equal(t, 1, result[r1.ID].TotalAllTime)
	assert.Equal(t, 1, result[r2.ID].TotalAllTime)
}

// === FindCompletionByID not found ===

func TestTaskRepository_FindCompletionByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	_, err := repo.FindCompletionByID(9999)
	assert.Error(t, err)
}

// === DeleteCompletion deletes images ===

func TestTaskRepository_DeleteCompletion_DeletesImages(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewTaskRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")

	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
	}
	require.NoError(t, db.Create(completion).Error)

	// Add images
	img := &models.TaskCompletionImage{
		CompletionID: completion.ID,
		ImageURL:     "https://example.com/img.jpg",
	}
	require.NoError(t, db.Create(img).Error)

	// Delete completion (should cascade to images)
	err := repo.DeleteCompletion(completion.ID)
	require.NoError(t, err)

	// Images should be gone
	var count int64
	db.Model(&models.TaskCompletionImage{}).Where("completion_id = ?", completion.ID).Count(&count)
	assert.Equal(t, int64(0), count)
}
