package services

import (
	"net/http"
	"testing"
	"time"

	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

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

func setupTaskService(t *testing.T) (*TaskService, *repositories.TaskRepository, *repositories.ResidenceRepository) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)
	return service, taskRepo, residenceRepo
}

func TestTaskService_CreateTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Fix leaky faucet",
		Description: "Kitchen faucet is dripping",
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, resp.Data.ID)
	assert.Equal(t, "Fix leaky faucet", resp.Data.Title)
	assert.Equal(t, "Kitchen faucet is dripping", resp.Data.Description)
}

func TestTaskService_CreateTask_WithOptionalFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Get category and priority IDs
	var category models.TaskCategory
	var priority models.TaskPriority
	db.First(&category)
	db.First(&priority)

	dueDate := requests.FlexibleDate{Time: time.Now().Add(7 * 24 * time.Hour).UTC()}
	cost := decimal.NewFromFloat(150.50)

	req := &requests.CreateTaskRequest{
		ResidenceID:   residence.ID,
		Title:         "Fix leaky faucet",
		CategoryID:    &category.ID,
		PriorityID:    &priority.ID,
		DueDate:       &dueDate,
		EstimatedCost: &cost,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	// Note: Category and Priority are no longer preloaded for performance
	// Client resolves from cache using CategoryID and PriorityID
	assert.NotNil(t, resp.Data.CategoryID, "CategoryID should be set")
	assert.NotNil(t, resp.Data.PriorityID, "PriorityID should be set")
	assert.NotNil(t, resp.Data.DueDate)
	assert.NotNil(t, resp.Data.EstimatedCost)
}

func TestTaskService_CreateTask_WithTemplateID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Create a template inline; testutil migrates the TaskTemplate model but
	// doesn't seed any rows.
	tmpl := models.TaskTemplate{Title: "Change HVAC Filter", IsActive: true}
	require.NoError(t, db.Create(&tmpl).Error)

	tests := []struct {
		name       string
		templateID *uint
		wantID     *uint
	}{
		{name: "template set", templateID: &tmpl.ID, wantID: &tmpl.ID},
		{name: "template nil (custom task)", templateID: nil, wantID: nil},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := &requests.CreateTaskRequest{
				ResidenceID: residence.ID,
				Title:       "From template: " + tc.name,
				TemplateID:  tc.templateID,
			}
			resp, err := service.CreateTask(req, user.ID, time.Now().UTC())
			require.NoError(t, err)

			if tc.wantID == nil {
				assert.Nil(t, resp.Data.TemplateID, "TemplateID should not be set on custom tasks")
			} else {
				require.NotNil(t, resp.Data.TemplateID)
				assert.Equal(t, *tc.wantID, *resp.Data.TemplateID)
			}

			// Verify persistence directly against the DB
			var stored models.Task
			require.NoError(t, db.First(&stored, resp.Data.ID).Error)
			if tc.wantID == nil {
				assert.Nil(t, stored.TaskTemplateID)
			} else {
				require.NotNil(t, stored.TaskTemplateID)
				assert.Equal(t, *tc.wantID, *stored.TaskTemplateID)
			}
		})
	}
}

func TestTaskService_BulkCreateTasks(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	tmpl := models.TaskTemplate{Title: "Change HVAC Filter", IsActive: true}
	require.NoError(t, db.Create(&tmpl).Error)

	t.Run("happy path creates all tasks atomically", func(t *testing.T) {
		req := &requests.BulkCreateTasksRequest{
			ResidenceID: residence.ID,
			Tasks: []requests.CreateTaskRequest{
				{ResidenceID: residence.ID, Title: "Task A", TemplateID: &tmpl.ID},
				{ResidenceID: residence.ID, Title: "Task B"},
				{ResidenceID: residence.ID, Title: "Task C"},
			},
		}
		resp, err := service.BulkCreateTasks(req, user.ID, time.Now().UTC())
		require.NoError(t, err)
		assert.Equal(t, 3, resp.CreatedCount)
		assert.Len(t, resp.Tasks, 3)
		// First task carried the template backlink through.
		require.NotNil(t, resp.Tasks[0].TemplateID)
		assert.Equal(t, tmpl.ID, *resp.Tasks[0].TemplateID)
		// Other two have no template.
		assert.Nil(t, resp.Tasks[1].TemplateID)
		assert.Nil(t, resp.Tasks[2].TemplateID)
	})

	t.Run("rollback on validation failure inside batch", func(t *testing.T) {
		// Count tasks before the failing batch.
		var before int64
		db.Model(&models.Task{}).Where("residence_id = ?", residence.ID).Count(&before)

		// Empty title is invalid at the DB layer if title has not-null
		// constraint. In SQLite the column is nullable, so instead we force a
		// failure via a duplicate primary key after manually inserting one.
		// Simplest cross-dialect trick: insert a task, then attempt a bulk
		// with an entry whose ID conflicts. Use a manual task with huge
		// NextDueDate to make it easy to spot.
		//
		// For this test we rely on the service short-circuiting when any
		// CreateTx returns an error. Trigger that by temporarily dropping
		// the title column's default — skipped here because SQLite is
		// lenient. Instead we validate the transactional boundary by
		// ensuring an *empty* tasks list produces a 400 and does not write.
		req := &requests.BulkCreateTasksRequest{
			ResidenceID: residence.ID,
			Tasks:       []requests.CreateTaskRequest{}, // empty triggers the guard
		}
		_, err := service.BulkCreateTasks(req, user.ID, time.Now().UTC())
		testutil.AssertAppError(t, err, http.StatusBadRequest, "error.task_list_empty")

		var after int64
		db.Model(&models.Task{}).Where("residence_id = ?", residence.ID).Count(&after)
		assert.Equal(t, before, after, "no tasks should have been created")
	})

	t.Run("access denied for foreign residence", func(t *testing.T) {
		other := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
		req := &requests.BulkCreateTasksRequest{
			ResidenceID: residence.ID,
			Tasks: []requests.CreateTaskRequest{
				{ResidenceID: residence.ID, Title: "Sneaky"},
			},
		}
		_, err := service.BulkCreateTasks(req, other.ID, time.Now().UTC())
		testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
	})

	t.Run("overrides per-entry residence_id with batch value", func(t *testing.T) {
		// Create a second residence the user has access to.
		second := testutil.CreateTestResidence(t, db, user.ID, "Second House")
		req := &requests.BulkCreateTasksRequest{
			ResidenceID: residence.ID,
			Tasks: []requests.CreateTaskRequest{
				{ResidenceID: second.ID, Title: "Should land on batch residence"},
			},
		}
		resp, err := service.BulkCreateTasks(req, user.ID, time.Now().UTC())
		require.NoError(t, err)
		require.Len(t, resp.Tasks, 1)
		assert.Equal(t, residence.ID, resp.Tasks[0].ResidenceID)
	})
}

func TestTaskService_CreateTask_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Test Task",
	}

	now := time.Now().UTC()
	_, err := service.CreateTask(req, otherUser.ID, now)
	// When creating a task, residence access is checked first
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

func TestTaskService_GetTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	resp, err := service.GetTask(task.ID, user.ID)
	require.NoError(t, err)
	assert.Equal(t, task.ID, resp.ID)
	assert.Equal(t, "Test Task", resp.Title)
}

func TestTaskService_GetTask_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	_, err := service.GetTask(task.ID, otherUser.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.task_access_denied")
}

func TestTaskService_ListTasks(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2")
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 3")

	resp, err := service.ListTasks(user.ID, 30, time.Now().UTC())
	require.NoError(t, err)
	// ListTasks returns a KanbanBoardResponse with columns
	// Count total tasks across all columns
	totalTasks := 0
	for _, col := range resp.Columns {
		totalTasks += col.Count
	}
	assert.Equal(t, 3, totalTasks)
}

func TestTaskService_UpdateTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	newTitle := "Updated Title"
	newDesc := "Updated description"
	req := &requests.UpdateTaskRequest{
		Title:       &newTitle,
		Description: &newDesc,
	}

	now := time.Now().UTC()
	resp, err := service.UpdateTask(task.ID, user.ID, req, now)
	require.NoError(t, err)
	assert.Equal(t, "Updated Title", resp.Data.Title)
	assert.Equal(t, "Updated description", resp.Data.Description)
}

func TestTaskService_DeleteTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	_, err := service.DeleteTask(task.ID, user.ID)
	require.NoError(t, err)

	_, err = service.GetTask(task.ID, user.ID)
	assert.Error(t, err)
}

func TestTaskService_CancelTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	resp, err := service.CancelTask(task.ID, user.ID, now)
	require.NoError(t, err)
	assert.True(t, resp.Data.IsCancelled)
}

func TestTaskService_CancelTask_AlreadyCancelled(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	service.CancelTask(task.ID, user.ID, now)
	_, err := service.CancelTask(task.ID, user.ID, now)
	testutil.AssertAppError(t, err, http.StatusBadRequest, "error.task_already_cancelled")
}

func TestTaskService_UncancelTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	service.CancelTask(task.ID, user.ID, now)
	resp, err := service.UncancelTask(task.ID, user.ID, now)
	require.NoError(t, err)
	assert.False(t, resp.Data.IsCancelled)
}

func TestTaskService_ArchiveTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	resp, err := service.ArchiveTask(task.ID, user.ID, now)
	require.NoError(t, err)
	assert.True(t, resp.Data.IsArchived)
}

func TestTaskService_UnarchiveTask(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	service.ArchiveTask(task.ID, user.ID, now)
	resp, err := service.UnarchiveTask(task.ID, user.ID, now)
	require.NoError(t, err)
	assert.False(t, resp.Data.IsArchived)
}

func TestTaskService_MarkInProgress(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	resp, err := service.MarkInProgress(task.ID, user.ID, now)
	require.NoError(t, err)
	assert.True(t, resp.Data.InProgress)
}

func TestTaskService_CreateCompletion(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	req := &requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Completed successfully",
	}

	now := time.Now().UTC()
	resp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, resp.Data.ID)
	assert.Equal(t, task.ID, resp.Data.TaskID)
	assert.Equal(t, "Completed successfully", resp.Data.Notes)
}

func TestTaskService_CreateCompletion_RecurringTask_ResetsInProgress(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var monthlyFrequency models.TaskFrequency
	db.Where("name = ?", "Monthly").First(&monthlyFrequency)

	// Create a recurring task that is in progress
	dueDate := time.Now().AddDate(0, 0, 7) // Due in 7 days
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Recurring Task",
		InProgress:  true,
		FrequencyID: &monthlyFrequency.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete the task
	req := &requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Monthly maintenance done",
	}

	now := time.Now().UTC()
	resp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, resp.Data.ID)

	// Verify the task in the response has InProgress reset to false
	require.NotNil(t, resp.Data.Task, "Response should include the updated task")
	assert.False(t, resp.Data.Task.InProgress, "Recurring task InProgress should be reset to false after completion")

	// Verify NextDueDate was updated (should be ~30 days from now for monthly)
	require.NotNil(t, resp.Data.Task.NextDueDate, "Recurring task should have NextDueDate set")
	expectedNextDue := time.Now().AddDate(0, 0, 30) // Monthly = 30 days
	assert.WithinDuration(t, expectedNextDue, *resp.Data.Task.NextDueDate, 24*time.Hour, "NextDueDate should be approximately 30 days from now")

	// Also verify by reloading from database directly
	var reloadedTask models.Task
	db.First(&reloadedTask, task.ID)
	assert.False(t, reloadedTask.InProgress, "Database should show InProgress=false")
}

func TestTaskService_GetCompletion(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	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:         "Test notes",
	}
	db.Create(completion)

	resp, err := service.GetCompletion(completion.ID, user.ID)
	require.NoError(t, err)
	assert.Equal(t, completion.ID, resp.ID)
	assert.Equal(t, "Test notes", resp.Notes)
}

func TestTaskService_DeleteCompletion(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	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(),
	}
	db.Create(completion)

	_, err := service.DeleteCompletion(completion.ID, user.ID)
	require.NoError(t, err)

	_, err = service.GetCompletion(completion.ID, user.ID)
	assert.Error(t, err)
}

func TestTaskService_CreateCompletion_TransactionIntegrity(t *testing.T) {
	// Verifies P1-5 / P1-6: completion creation and task update are atomic.
	// After completion, both the completion record AND the task's NextDueDate update
	// should succeed together, and errors should be propagated (not swallowed).
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Create a one-time task with a due date
	dueDate := time.Now().AddDate(0, 0, 7).UTC()
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "One-time Task",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	req := &requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Done",
	}

	now := time.Now().UTC()
	resp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, resp.Data.ID)

	// Verify the task was updated: NextDueDate should be nil for a one-time task
	var reloaded models.Task
	db.First(&reloaded, task.ID)
	assert.Nil(t, reloaded.NextDueDate, "One-time task NextDueDate should be nil after completion")
	assert.False(t, reloaded.InProgress, "InProgress should be false after completion")

	// Verify completion record exists
	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err, "Completion record should exist")
	assert.Equal(t, "Done", completion.Notes)
}

func TestTaskService_CreateCompletion_UpdateError_ReturnedNotSwallowed(t *testing.T) {
	// Verifies P1-5 and P1-6: the completion creation and task update are wrapped
	// in a transaction, and update errors are returned (not swallowed).
	//
	// Strategy: We trigger a version conflict by using a goroutine that bumps
	// the task version after the service reads the task but during the transaction.
	// Since SQLite serializes writes, we instead verify the behavior by deleting
	// the task between the service read and the transactional update. When UpdateTx
	// tries to match the row by id+version, 0 rows are affected and ErrVersionConflict
	// is returned. The transaction then rolls back the completion insert.
	//
	// However, because the entire CreateCompletion flow is synchronous and we cannot
	// inject failures between steps, we instead verify the transactional guarantee
	// indirectly: we confirm that a concurrent version bump (set before the call
	// but after the SELECT) causes the version conflict to propagate. Since FindByID
	// re-reads the current version, we must verify via a custom test that invokes
	// the transaction layer directly.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)

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

	dueDate := time.Now().AddDate(0, 0, 7).UTC()
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Conflict Task",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Directly test that the transactional path returns an error on version conflict:
	// Use a stale task object (version=1) when the DB has been bumped to version=999.
	db.Model(&models.Task{}).Where("id = ?", task.ID).Update("version", 999)

	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
		Notes:         "Should be rolled back",
	}

	// Simulate the transaction that CreateCompletion now uses (task still has version=1)
	txErr := taskRepo.DB().Transaction(func(tx *gorm.DB) error {
		if err := taskRepo.CreateCompletionTx(tx, completion); err != nil {
			return err
		}
		// task.Version is 1 but DB has 999 -> version conflict
		if err := taskRepo.UpdateTx(tx, task); err != nil {
			return err
		}
		return nil
	})

	require.Error(t, txErr, "Transaction should fail due to version conflict")
	assert.ErrorIs(t, txErr, repositories.ErrVersionConflict, "Error should be ErrVersionConflict")

	// Verify the completion was rolled back
	var count int64
	db.Model(&models.TaskCompletion{}).Where("task_id = ?", task.ID).Count(&count)
	assert.Equal(t, int64(0), count, "Completion should not exist when transaction rolls back")

	// Also verify that CreateCompletion (full service method) would propagate the error.
	// Re-create the task with a normal version so FindByID works, then bump it.
	db.Model(&models.Task{}).Where("id = ?", task.ID).Update("version", 1)
	service := NewTaskService(taskRepo, residenceRepo)
	req := &requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Test error propagation",
	}
	now := time.Now().UTC()
	// This call will succeed because FindByID loads version=1, UpdateTx uses version=1, DB has version=1.
	// To verify error propagation, we use the direct transaction test above.
	resp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err, "CreateCompletion should succeed with matching versions")
	assert.NotZero(t, resp.Data.ID)
}

func TestTaskService_DeleteCompletion_OneTime_RestoresOriginalDueDate(t *testing.T) {
	// Verifies P1-7: deleting the only completion on a one-time task
	// should restore NextDueDate to the original DueDate.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Create a one-time task with a due date
	originalDueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "One-time Task",
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
		// No FrequencyID = one-time task
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete the task (sets NextDueDate to nil for one-time tasks)
	req := &requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Completed",
	}
	now := time.Now().UTC()
	completionResp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err)

	// Confirm NextDueDate is nil after completion
	var taskAfterComplete models.Task
	db.First(&taskAfterComplete, task.ID)
	assert.Nil(t, taskAfterComplete.NextDueDate, "NextDueDate should be nil after one-time completion")

	// Delete the completion
	_, err = service.DeleteCompletion(completionResp.Data.ID, user.ID)
	require.NoError(t, err)

	// Verify NextDueDate is restored to the original DueDate
	var taskAfterDelete models.Task
	db.First(&taskAfterDelete, task.ID)
	require.NotNil(t, taskAfterDelete.NextDueDate, "NextDueDate should be restored after deleting completion")
	assert.Equal(t, originalDueDate.Year(), taskAfterDelete.NextDueDate.Year())
	assert.Equal(t, originalDueDate.Month(), taskAfterDelete.NextDueDate.Month())
	assert.Equal(t, originalDueDate.Day(), taskAfterDelete.NextDueDate.Day())
}

func TestTaskService_DeleteCompletion_Recurring_RecalculatesFromLastCompletion(t *testing.T) {
	// Verifies P1-7: deleting the latest completion on a recurring task
	// should recalculate NextDueDate from the remaining latest completion.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var monthlyFrequency models.TaskFrequency
	db.Where("name = ?", "Monthly").First(&monthlyFrequency)

	// Create a recurring task
	originalDueDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Recurring Task",
		FrequencyID: &monthlyFrequency.ID,
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// First completion on Jan 15
	firstCompletedAt := time.Date(2026, 1, 15, 10, 0, 0, 0, time.UTC)
	firstReq := &requests.CreateTaskCompletionRequest{
		TaskID:      task.ID,
		Notes:       "First completion",
		CompletedAt: &firstCompletedAt,
	}
	now := time.Now().UTC()
	_, err = service.CreateCompletion(firstReq, user.ID, now)
	require.NoError(t, err)

	// Second completion on Feb 15
	secondCompletedAt := time.Date(2026, 2, 15, 10, 0, 0, 0, time.UTC)
	secondReq := &requests.CreateTaskCompletionRequest{
		TaskID:      task.ID,
		Notes:       "Second completion",
		CompletedAt: &secondCompletedAt,
	}
	resp, err := service.CreateCompletion(secondReq, user.ID, now)
	require.NoError(t, err)

	// NextDueDate should be Feb 15 + 30 days = Mar 17
	var taskAfterSecond models.Task
	db.First(&taskAfterSecond, task.ID)
	require.NotNil(t, taskAfterSecond.NextDueDate)
	expectedAfterSecond := secondCompletedAt.AddDate(0, 0, 30)
	assert.Equal(t, expectedAfterSecond.Year(), taskAfterSecond.NextDueDate.Year())
	assert.Equal(t, expectedAfterSecond.Month(), taskAfterSecond.NextDueDate.Month())
	assert.Equal(t, expectedAfterSecond.Day(), taskAfterSecond.NextDueDate.Day())

	// Delete the second (latest) completion
	_, err = service.DeleteCompletion(resp.Data.ID, user.ID)
	require.NoError(t, err)

	// NextDueDate should be recalculated from the first completion: Jan 15 + 30 = Feb 14
	var taskAfterDelete models.Task
	db.First(&taskAfterDelete, task.ID)
	require.NotNil(t, taskAfterDelete.NextDueDate, "NextDueDate should be set after deleting latest completion")
	expectedRecalculated := firstCompletedAt.AddDate(0, 0, 30)
	assert.Equal(t, expectedRecalculated.Year(), taskAfterDelete.NextDueDate.Year())
	assert.Equal(t, expectedRecalculated.Month(), taskAfterDelete.NextDueDate.Month())
	assert.Equal(t, expectedRecalculated.Day(), taskAfterDelete.NextDueDate.Day())
}

func TestTaskService_DeleteCompletion_LastCompletion_RestoresDueDate(t *testing.T) {
	// Verifies P1-7: deleting the only completion on a recurring task
	// should restore NextDueDate to the original DueDate.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	// Create a recurring task
	originalDueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Weekly Task",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete the task
	completedAt := time.Date(2026, 3, 2, 10, 0, 0, 0, time.UTC)
	req := &requests.CreateTaskCompletionRequest{
		TaskID:      task.ID,
		Notes:       "First completion",
		CompletedAt: &completedAt,
	}
	now := time.Now().UTC()
	completionResp, err := service.CreateCompletion(req, user.ID, now)
	require.NoError(t, err)

	// Verify NextDueDate was set to completedAt + 7 days
	var taskAfterComplete models.Task
	db.First(&taskAfterComplete, task.ID)
	require.NotNil(t, taskAfterComplete.NextDueDate)

	// Delete the only completion
	_, err = service.DeleteCompletion(completionResp.Data.ID, user.ID)
	require.NoError(t, err)

	// NextDueDate should be restored to original DueDate since no completions remain
	var taskAfterDelete models.Task
	db.First(&taskAfterDelete, task.ID)
	require.NotNil(t, taskAfterDelete.NextDueDate, "NextDueDate should be restored to original DueDate")
	assert.Equal(t, originalDueDate.Year(), taskAfterDelete.NextDueDate.Year())
	assert.Equal(t, originalDueDate.Month(), taskAfterDelete.NextDueDate.Month())
	assert.Equal(t, originalDueDate.Day(), taskAfterDelete.NextDueDate.Day())
}

func TestTaskService_GetCategories(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	categories, err := service.GetCategories()
	require.NoError(t, err)
	assert.Greater(t, len(categories), 0)

	// Check JSON structure
	for _, cat := range categories {
		assert.NotZero(t, cat.ID)
		assert.NotEmpty(t, cat.Name)
	}
}

func TestTaskService_GetPriorities(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	priorities, err := service.GetPriorities()
	require.NoError(t, err)
	assert.Greater(t, len(priorities), 0)

	// Check order by level
	for i := 1; i < len(priorities); i++ {
		assert.GreaterOrEqual(t, priorities[i].Level, priorities[i-1].Level)
	}
}

func TestTaskService_GetFrequencies(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	frequencies, err := service.GetFrequencies()
	require.NoError(t, err)
	assert.Greater(t, len(frequencies), 0)
}

func TestTaskService_SharedUserAccess(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")

	// Share residence
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	// Create task as owner
	task := testutil.CreateTestTask(t, db, residence.ID, owner.ID, "Test Task")

	// Shared user should be able to see the task
	resp, err := service.GetTask(task.ID, sharedUser.ID)
	require.NoError(t, err)
	assert.Equal(t, task.ID, resp.ID)

	// Shared user should be able to create tasks
	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Shared User Task",
	}
	now := time.Now().UTC()
	_, err = service.CreateTask(req, sharedUser.ID, now)
	require.NoError(t, err)
}

// =============================================================================
// Task Creation: NextDueDate Initialization
// =============================================================================

func TestTaskService_CreateTask_NextDueDateEqualsDueDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, 14).UTC()}
	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Task with due date",
		DueDate:     &dueDate,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	require.NotNil(t, resp.Data.DueDate, "DueDate should be set")
	require.NotNil(t, resp.Data.NextDueDate, "NextDueDate should be initialized")
	assert.Equal(t, resp.Data.DueDate.Format("2006-01-02"), resp.Data.NextDueDate.Format("2006-01-02"),
		"NextDueDate should equal DueDate on creation")
}

func TestTaskService_CreateTask_NoDueDate_NextDueDateNil(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Task without due date",
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.Nil(t, resp.Data.DueDate, "DueDate should be nil")
	assert.Nil(t, resp.Data.NextDueDate, "NextDueDate should be nil when no DueDate")
}

// =============================================================================
// Task Creation: Recurring Tasks
// =============================================================================

func TestTaskService_CreateTask_WithWeeklyFrequency(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, 7).UTC()}
	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Weekly cleaning",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.NotNil(t, resp.Data.FrequencyID, "FrequencyID should be saved")
	assert.Equal(t, weeklyFreq.ID, *resp.Data.FrequencyID, "FrequencyID should match Weekly")
}

func TestTaskService_CreateTask_WithCustomFrequencyAndInterval(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Create a custom frequency
	days14 := 14
	customFreq := &models.TaskFrequency{Name: "Custom", Days: &days14, DisplayOrder: 10}
	db.Create(customFreq)

	customDays := 14
	dueDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, 7).UTC()}
	req := &requests.CreateTaskRequest{
		ResidenceID:        residence.ID,
		Title:              "Bi-weekly inspection",
		FrequencyID:        &customFreq.ID,
		CustomIntervalDays: &customDays,
		DueDate:            &dueDate,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.NotNil(t, resp.Data.FrequencyID, "FrequencyID should be saved")
	assert.NotNil(t, resp.Data.CustomIntervalDays, "CustomIntervalDays should be saved")
	assert.Equal(t, 14, *resp.Data.CustomIntervalDays, "CustomIntervalDays should be 14")
}

func TestTaskService_CreateTask_FrequencyWithoutDueDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Recurring no due date",
		FrequencyID: &weeklyFreq.ID,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.NotNil(t, resp.Data.FrequencyID, "FrequencyID should be saved")
	assert.Nil(t, resp.Data.NextDueDate, "NextDueDate should be nil when no DueDate")
}

// =============================================================================
// Task Creation: Edge Cases
// =============================================================================

func TestTaskService_CreateTask_WithPastDueDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	pastDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, -30).UTC()}
	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Overdue from start",
		DueDate:     &pastDate,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err, "Creating task with past due date should not error")
	require.NotNil(t, resp.Data.DueDate, "DueDate should be set")
	assert.True(t, resp.Data.DueDate.Before(time.Now()), "DueDate should be in the past")
}

func TestTaskService_CreateTask_WithInProgressTrue(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Already started",
		InProgress:  true,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)
	assert.True(t, resp.Data.InProgress, "InProgress should be true in response")

	// Verify in database
	var reloaded models.Task
	db.First(&reloaded, resp.Data.ID)
	assert.True(t, reloaded.InProgress, "InProgress should be true in DB")
}

func TestTaskService_CreateTask_AllOptionalFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	assignee := testutil.CreateTestUser(t, db, "assignee", "assignee@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	// Add assignee to residence so they have access
	residenceRepo.AddUser(residence.ID, assignee.ID)

	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Joe's Plumbing")

	var category models.TaskCategory
	var priority models.TaskPriority
	var weeklyFreq models.TaskFrequency
	db.First(&category)
	db.First(&priority)
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, 7).UTC()}
	cost := decimal.NewFromFloat(250.00)

	req := &requests.CreateTaskRequest{
		ResidenceID:   residence.ID,
		Title:         "Full task with all fields",
		Description:   "A comprehensive task",
		CategoryID:    &category.ID,
		PriorityID:    &priority.ID,
		FrequencyID:   &weeklyFreq.ID,
		InProgress:    true,
		AssignedToID:  &assignee.ID,
		DueDate:       &dueDate,
		EstimatedCost: &cost,
		ContractorID:  &contractor.ID,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err)

	assert.Equal(t, "Full task with all fields", resp.Data.Title)
	assert.Equal(t, "A comprehensive task", resp.Data.Description)
	assert.NotNil(t, resp.Data.CategoryID, "CategoryID should be set")
	assert.Equal(t, category.ID, *resp.Data.CategoryID)
	assert.NotNil(t, resp.Data.PriorityID, "PriorityID should be set")
	assert.Equal(t, priority.ID, *resp.Data.PriorityID)
	assert.NotNil(t, resp.Data.FrequencyID, "FrequencyID should be set")
	assert.Equal(t, weeklyFreq.ID, *resp.Data.FrequencyID)
	assert.True(t, resp.Data.InProgress, "InProgress should be true")
	assert.NotNil(t, resp.Data.AssignedToID, "AssignedToID should be set")
	assert.Equal(t, assignee.ID, *resp.Data.AssignedToID)
	assert.NotNil(t, resp.Data.DueDate, "DueDate should be set")
	assert.NotNil(t, resp.Data.EstimatedCost, "EstimatedCost should be set")
	assert.NotNil(t, resp.Data.ContractorID, "ContractorID should be set")
	assert.Equal(t, contractor.ID, *resp.Data.ContractorID)
}

func TestTaskService_CreateTask_CustomIntervalDaysWithoutFrequency(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	customDays := 10
	req := &requests.CreateTaskRequest{
		ResidenceID:        residence.ID,
		Title:              "Custom interval without frequency",
		CustomIntervalDays: &customDays,
	}

	now := time.Now().UTC()
	resp, err := service.CreateTask(req, user.ID, now)
	require.NoError(t, err, "Should save even without FrequencyID")
	assert.NotNil(t, resp.Data.CustomIntervalDays, "CustomIntervalDays should be saved")
	assert.Equal(t, 10, *resp.Data.CustomIntervalDays)
	assert.Nil(t, resp.Data.FrequencyID, "FrequencyID should be nil")
}

// =============================================================================
// Task Creation: Validation (service-level)
// =============================================================================

func TestTaskService_CreateTask_InvalidResidenceAccess(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	stranger := testutil.CreateTestUser(t, db, "stranger", "stranger@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Owner's House")

	req := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Should be denied",
	}

	now := time.Now().UTC()
	_, err := service.CreateTask(req, stranger.ID, now)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

func TestTaskService_CreateTask_NonExistentResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	req := &requests.CreateTaskRequest{
		ResidenceID: 99999,
		Title:       "Nowhere to live",
	}

	now := time.Now().UTC()
	_, err := service.CreateTask(req, user.ID, now)
	require.Error(t, err, "Should error for non-existent residence")
	// Should return forbidden since user has no access to non-existent residence
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

// =============================================================================
// Part 1: Completion Deletion Tests (TDD)
// =============================================================================

func TestTaskService_DeleteCompletion_OneTime_RestoresAndExitsKanbanCompleted(t *testing.T) {
	// Verifies: deleting the only completion on a one-time task restores NextDueDate
	// to original DueDate, and the task is no longer in the "completed" kanban state.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	originalDueDate := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "One-time KanbanCheck",
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete the task (sets NextDueDate to nil)
	now := time.Now().UTC()
	completionResp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
		Notes:  "Done",
	}, user.ID, now)
	require.NoError(t, err)

	// Confirm completed state: NextDueDate nil, has completion
	var taskAfterComplete models.Task
	db.Preload("Completions").First(&taskAfterComplete, task.ID)
	assert.Nil(t, taskAfterComplete.NextDueDate, "NextDueDate should be nil after one-time completion")
	assert.True(t, len(taskAfterComplete.Completions) > 0, "Should have completion")
	// IsCompleted: NextDueDate == nil && HasCompletions
	assert.True(t, taskAfterComplete.NextDueDate == nil && len(taskAfterComplete.Completions) > 0,
		"Task should be in completed state")

	// Delete the completion
	_, err = service.DeleteCompletion(completionResp.Data.ID, user.ID)
	require.NoError(t, err)

	// Verify NextDueDate restored to original DueDate
	var taskAfterDelete models.Task
	db.Preload("Completions").First(&taskAfterDelete, task.ID)
	require.NotNil(t, taskAfterDelete.NextDueDate, "NextDueDate should be restored")
	assert.Equal(t, originalDueDate.Year(), taskAfterDelete.NextDueDate.Year())
	assert.Equal(t, originalDueDate.Month(), taskAfterDelete.NextDueDate.Month())
	assert.Equal(t, originalDueDate.Day(), taskAfterDelete.NextDueDate.Day())

	// Verify no completions remain
	assert.Equal(t, 0, len(taskAfterDelete.Completions), "No completions should remain")

	// Verify NOT in completed state anymore
	assert.False(t, taskAfterDelete.NextDueDate == nil && len(taskAfterDelete.Completions) > 0,
		"Task should NOT be in completed state after deleting its only completion")
}

func TestTaskService_DeleteCompletion_Recurring_RestoresToOriginalDueDate(t *testing.T) {
	// Verifies: deleting the only completion on a recurring task restores NextDueDate
	// to the original DueDate.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	originalDueDate := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Recurring Restore Test",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete on Apr 3
	completedAt := time.Date(2026, 4, 3, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	completionResp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Weekly done", CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	// Confirm NextDueDate advanced to Apr 10 (Apr 3 + 7)
	var taskAfterComplete models.Task
	db.First(&taskAfterComplete, task.ID)
	require.NotNil(t, taskAfterComplete.NextDueDate)
	assert.Equal(t, 10, taskAfterComplete.NextDueDate.Day())

	// Delete the completion
	_, err = service.DeleteCompletion(completionResp.Data.ID, user.ID)
	require.NoError(t, err)

	// Verify NextDueDate restored to original DueDate (Apr 1)
	var taskAfterDelete models.Task
	db.First(&taskAfterDelete, task.ID)
	require.NotNil(t, taskAfterDelete.NextDueDate)
	assert.Equal(t, originalDueDate.Year(), taskAfterDelete.NextDueDate.Year())
	assert.Equal(t, originalDueDate.Month(), taskAfterDelete.NextDueDate.Month())
	assert.Equal(t, originalDueDate.Day(), taskAfterDelete.NextDueDate.Day())
}

func TestTaskService_DeleteCompletion_MultipleCompletions_SequentialDeletion(t *testing.T) {
	// Verifies: deleting completions one-by-one from newest to oldest on a recurring task
	// properly recalculates NextDueDate at each step.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	originalDueDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "3-completion Task",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()

	// Completion 1: Jan 5
	c1At := time.Date(2026, 1, 5, 10, 0, 0, 0, time.UTC)
	c1Resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Completion 1", CompletedAt: &c1At,
	}, user.ID, now)
	require.NoError(t, err)

	// Completion 2: Jan 12
	c2At := time.Date(2026, 1, 12, 10, 0, 0, 0, time.UTC)
	c2Resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Completion 2", CompletedAt: &c2At,
	}, user.ID, now)
	require.NoError(t, err)

	// Completion 3: Jan 19
	c3At := time.Date(2026, 1, 19, 10, 0, 0, 0, time.UTC)
	c3Resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Completion 3", CompletedAt: &c3At,
	}, user.ID, now)
	require.NoError(t, err)

	// After 3 completions: NextDueDate = Jan 19 + 7 = Jan 26
	var taskAfter3 models.Task
	db.First(&taskAfter3, task.ID)
	require.NotNil(t, taskAfter3.NextDueDate)
	assert.Equal(t, 26, taskAfter3.NextDueDate.Day())

	// Delete completion 3 (latest) -> recalc from completion 2: Jan 12 + 7 = Jan 19
	_, err = service.DeleteCompletion(c3Resp.Data.ID, user.ID)
	require.NoError(t, err)
	var taskAfterDel3 models.Task
	db.First(&taskAfterDel3, task.ID)
	require.NotNil(t, taskAfterDel3.NextDueDate)
	assert.Equal(t, 19, taskAfterDel3.NextDueDate.Day(),
		"NextDueDate should be Jan 19 (completion 2: Jan 12 + 7)")

	// Delete completion 2 (now latest) -> recalc from completion 1: Jan 5 + 7 = Jan 12
	_, err = service.DeleteCompletion(c2Resp.Data.ID, user.ID)
	require.NoError(t, err)
	var taskAfterDel2 models.Task
	db.First(&taskAfterDel2, task.ID)
	require.NotNil(t, taskAfterDel2.NextDueDate)
	assert.Equal(t, 12, taskAfterDel2.NextDueDate.Day(),
		"NextDueDate should be Jan 12 (completion 1: Jan 5 + 7)")

	// Delete completion 1 (last remaining) -> restore to original DueDate: Jan 1
	_, err = service.DeleteCompletion(c1Resp.Data.ID, user.ID)
	require.NoError(t, err)
	var taskAfterDel1 models.Task
	db.First(&taskAfterDel1, task.ID)
	require.NotNil(t, taskAfterDel1.NextDueDate)
	assert.Equal(t, 1, taskAfterDel1.NextDueDate.Day(),
		"NextDueDate should be restored to original DueDate Jan 1")
	assert.Equal(t, time.January, taskAfterDel1.NextDueDate.Month())
}

func TestTaskService_DeleteCompletion_MiddleCompletion_KeepsLatest(t *testing.T) {
	// Verifies: deleting the middle (2nd) completion leaves NextDueDate based on the latest (3rd).
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	originalDueDate := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Middle Deletion Task",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &originalDueDate,
		NextDueDate: &originalDueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()

	// Create 3 completions
	c1At := time.Date(2026, 2, 3, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "First", CompletedAt: &c1At,
	}, user.ID, now)
	require.NoError(t, err)

	c2At := time.Date(2026, 2, 10, 10, 0, 0, 0, time.UTC)
	c2Resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Second", CompletedAt: &c2At,
	}, user.ID, now)
	require.NoError(t, err)

	c3At := time.Date(2026, 2, 17, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Third", CompletedAt: &c3At,
	}, user.ID, now)
	require.NoError(t, err)

	// After 3 completions: NextDueDate = Feb 17 + 7 = Feb 24
	var taskBefore models.Task
	db.First(&taskBefore, task.ID)
	require.NotNil(t, taskBefore.NextDueDate)
	assert.Equal(t, 24, taskBefore.NextDueDate.Day())

	// Delete the MIDDLE completion (2nd)
	_, err = service.DeleteCompletion(c2Resp.Data.ID, user.ID)
	require.NoError(t, err)

	// NextDueDate should still be based on the latest (3rd): Feb 17 + 7 = Feb 24
	var taskAfter models.Task
	db.First(&taskAfter, task.ID)
	require.NotNil(t, taskAfter.NextDueDate)
	assert.Equal(t, 24, taskAfter.NextDueDate.Day(),
		"Deleting middle completion should not change NextDueDate when latest still exists")
}

func TestTaskService_DeleteCompletion_DoesNotRestoreInProgress(t *testing.T) {
	// Documents behavior: DeleteCompletion does NOT restore InProgress.
	// It only recalculates NextDueDate.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "InProgress Task",
		InProgress:  true,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete it (sets InProgress = false)
	now := time.Now().UTC()
	completionResp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Completed",
	}, user.ID, now)
	require.NoError(t, err)

	var taskAfterComplete models.Task
	db.First(&taskAfterComplete, task.ID)
	assert.False(t, taskAfterComplete.InProgress, "InProgress should be false after completion")

	// Delete the completion
	_, err = service.DeleteCompletion(completionResp.Data.ID, user.ID)
	require.NoError(t, err)

	// InProgress is NOT restored by DeleteCompletion
	var taskAfterDelete models.Task
	db.First(&taskAfterDelete, task.ID)
	assert.False(t, taskAfterDelete.InProgress,
		"DeleteCompletion does not restore InProgress; it only recalculates NextDueDate")
}

// =============================================================================
// Part 2: Edge Case Tests (TDD)
// =============================================================================

func TestTaskService_TaskWithNoDates(t *testing.T) {
	// Task with no DueDate, no frequency: upcoming before completion, completed after.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	createReq := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "No Date Task",
	}
	now := time.Now().UTC()
	createResp, err := service.CreateTask(createReq, user.ID, now)
	require.NoError(t, err)

	// Verify dates are nil
	assert.Nil(t, createResp.Data.DueDate, "DueDate should be nil")
	assert.Nil(t, createResp.Data.NextDueDate, "NextDueDate should be nil")

	// Kanban: upcoming (no date = upcoming for uncompleted tasks)
	assert.Equal(t, "upcoming_tasks", createResp.Data.KanbanColumn,
		"Task with no due date should be in upcoming column")

	// Complete it
	completionResp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: createResp.Data.ID, Notes: "Done",
	}, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, completionResp.Data.ID)

	// Verify NextDueDate remains nil (one-time, no frequency)
	var taskAfterComplete models.Task
	db.Preload("Completions").First(&taskAfterComplete, createResp.Data.ID)
	assert.Nil(t, taskAfterComplete.NextDueDate, "NextDueDate remains nil for dateless one-time task")
	assert.True(t, len(taskAfterComplete.Completions) > 0, "Should have completion")

	// Now in completed state: NextDueDate==nil && HasCompletions
	assert.True(t, taskAfterComplete.NextDueDate == nil && len(taskAfterComplete.Completions) > 0,
		"Task should be in completed state")
}

func TestTaskService_TaskDueExactlyToday_Boundary(t *testing.T) {
	// Task due today is NOT overdue (today is not "past").
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	today := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Due Today Task",
		DueDate:     &today,
		NextDueDate: &today,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// NOT overdue
	assert.False(t, task.IsOverdueAt(today), "Task due today should NOT be overdue")

	// Complete it
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Done today",
	}, user.ID, today)
	require.NoError(t, err)

	// Verify transition: NextDueDate nil (one-time)
	var taskAfter models.Task
	db.First(&taskAfter, task.ID)
	assert.Nil(t, taskAfter.NextDueDate, "One-time task NextDueDate should be nil after completion")
}

func TestTaskService_TaskDueYesterday_IsOverdue(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	today := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	yesterday := today.AddDate(0, 0, -1)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Due Yesterday Task",
		DueDate:     &yesterday,
		NextDueDate: &yesterday,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	assert.True(t, task.IsOverdueAt(today), "Task due yesterday should be overdue")

	// Complete it
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Done late",
	}, user.ID, today)
	require.NoError(t, err)

	// Completed task is not overdue
	var taskAfter models.Task
	db.Preload("Completions").First(&taskAfter, task.ID)
	assert.Nil(t, taskAfter.NextDueDate)
	assert.True(t, len(taskAfter.Completions) > 0)
	assert.False(t, taskAfter.IsOverdueAt(today), "Completed task should not be overdue")
}

func TestTaskService_TaskVeryFarFuture(t *testing.T) {
	// Task due in 2099 is in "upcoming" column.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	farFuture := time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC)
	createReq := &requests.CreateTaskRequest{
		ResidenceID: residence.ID,
		Title:       "Far Future Task",
		DueDate:     &requests.FlexibleDate{Time: farFuture},
	}
	now := time.Now().UTC()
	resp, err := service.CreateTask(createReq, user.ID, now)
	require.NoError(t, err)

	assert.Equal(t, "upcoming_tasks", resp.Data.KanbanColumn,
		"Task due in 2099 should be in upcoming column")
}

func TestTaskService_RecurringTask_CompletedLate(t *testing.T) {
	// Weekly task due Dec 10, completed Dec 15 -> NextDueDate = Dec 22 (CompletedAt + 7)
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	dueDate := time.Date(2026, 12, 10, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Late Completion Task",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 12, 15, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Late", CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	// NextDueDate = Dec 15 + 7 = Dec 22, NOT Dec 10 + 7 = Dec 17
	assert.Equal(t, 22, reloaded.NextDueDate.Day(),
		"NextDueDate should be CompletedAt+7 (Dec 22), not DueDate+7 (Dec 17)")
}

func TestTaskService_RecurringTask_CompletedEarly(t *testing.T) {
	// Weekly task due Dec 17, completed Dec 14 -> NextDueDate = Dec 21 (CompletedAt + 7)
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFrequency models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFrequency)

	dueDate := time.Date(2026, 12, 17, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Early Completion Task",
		FrequencyID: &weeklyFrequency.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 12, 14, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Early", CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	assert.Equal(t, 21, reloaded.NextDueDate.Day(),
		"NextDueDate should be CompletedAt+7 (Dec 21)")
	assert.Equal(t, time.December, reloaded.NextDueDate.Month())
}

func TestTaskService_CustomIntervalEdgeCases(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// Create a "Custom" frequency (Days=0, overridden by task's CustomIntervalDays)
	customDays := 0
	customFrequency := &models.TaskFrequency{
		Name:         "Custom",
		Days:         &customDays,
		DisplayOrder: 10,
	}
	err := db.Create(customFrequency).Error
	require.NoError(t, err)

	t.Run("CustomIntervalDays=1_daily", func(t *testing.T) {
		interval := 1
		dueDate := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID:        residence.ID,
			CreatedByID:        user.ID,
			Title:              "Daily Custom Task",
			FrequencyID:        &customFrequency.ID,
			CustomIntervalDays: &interval,
			DueDate:            &dueDate,
			NextDueDate:        &dueDate,
			IsCancelled:        false,
			IsArchived:         false,
			Version:            1,
		}
		err := db.Create(task).Error
		require.NoError(t, err)

		completedAt := time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC)
		now := time.Now().UTC()
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID, CompletedAt: &completedAt,
		}, user.ID, now)
		require.NoError(t, err)

		var reloaded models.Task
		db.First(&reloaded, task.ID)
		require.NotNil(t, reloaded.NextDueDate)
		assert.Equal(t, 2, reloaded.NextDueDate.Day(), "NextDueDate = Jun 1 + 1 = Jun 2")
	})

	t.Run("CustomIntervalDays=365_yearly", func(t *testing.T) {
		interval := 365
		dueDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID:        residence.ID,
			CreatedByID:        user.ID,
			Title:              "Yearly Custom Task",
			FrequencyID:        &customFrequency.ID,
			CustomIntervalDays: &interval,
			DueDate:            &dueDate,
			NextDueDate:        &dueDate,
			IsCancelled:        false,
			IsArchived:         false,
			Version:            1,
		}
		err := db.Create(task).Error
		require.NoError(t, err)

		completedAt := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
		now := time.Now().UTC()
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID, CompletedAt: &completedAt,
		}, user.ID, now)
		require.NoError(t, err)

		var reloaded models.Task
		db.First(&reloaded, task.ID)
		require.NotNil(t, reloaded.NextDueDate)
		assert.Equal(t, 2027, reloaded.NextDueDate.Year(), "NextDueDate year should be 2027")
		assert.Equal(t, time.January, reloaded.NextDueDate.Month())
		assert.Equal(t, 1, reloaded.NextDueDate.Day())
	})
}

func TestTaskService_FrequencyWithNilOrZeroDays(t *testing.T) {
	// Frequency with Days=nil or Days=0 is treated as one-time on completion.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	t.Run("frequency_with_nil_days", func(t *testing.T) {
		var onceFrequency models.TaskFrequency
		db.Where("name = ?", "Once").First(&onceFrequency)
		require.Nil(t, onceFrequency.Days, "Once frequency should have nil Days")

		dueDate := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID: residence.ID,
			CreatedByID: user.ID,
			Title:       "Once Frequency Task",
			FrequencyID: &onceFrequency.ID,
			DueDate:     &dueDate,
			NextDueDate: &dueDate,
			IsCancelled: false,
			IsArchived:  false,
			Version:     1,
		}
		err := db.Create(task).Error
		require.NoError(t, err)

		now := time.Now().UTC()
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID, Notes: "Done once",
		}, user.ID, now)
		require.NoError(t, err)

		var reloaded models.Task
		db.First(&reloaded, task.ID)
		assert.Nil(t, reloaded.NextDueDate,
			"Frequency with Days=nil should be treated as one-time: NextDueDate=nil")
	})

	t.Run("frequency_with_zero_days", func(t *testing.T) {
		zeroDays := 0
		zeroFreq := &models.TaskFrequency{
			Name: "ZeroTest", Days: &zeroDays, DisplayOrder: 99,
		}
		err := db.Create(zeroFreq).Error
		require.NoError(t, err)

		dueDate := time.Date(2026, 7, 15, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID: residence.ID,
			CreatedByID: user.ID,
			Title:       "Zero Days Frequency Task",
			FrequencyID: &zeroFreq.ID,
			DueDate:     &dueDate,
			NextDueDate: &dueDate,
			IsCancelled: false,
			IsArchived:  false,
			Version:     1,
		}
		err = db.Create(task).Error
		require.NoError(t, err)

		now := time.Now().UTC()
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID, Notes: "Done zero",
		}, user.ID, now)
		require.NoError(t, err)

		var reloaded models.Task
		db.First(&reloaded, task.ID)
		assert.Nil(t, reloaded.NextDueDate,
			"Frequency with Days=0 should be treated as one-time: NextDueDate=nil")
	})
}

func TestTaskService_VersionConflict(t *testing.T) {
	// Verifies: UpdateTx / Update checks version and returns ErrVersionConflict on stale version.
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	// First update succeeds
	newTitle := "Updated Once"
	now := time.Now().UTC()
	resp, err := service.UpdateTask(task.ID, user.ID, &requests.UpdateTaskRequest{
		Title: &newTitle,
	}, now)
	require.NoError(t, err)
	assert.Equal(t, "Updated Once", resp.Data.Title)

	// Simulate stale version: read current, then bump version in DB
	staleTask := &models.Task{}
	db.First(staleTask, task.ID)
	currentVersion := staleTask.Version

	db.Model(&models.Task{}).Where("id = ?", task.ID).Update("version", currentVersion+10)

	// Try update with stale version -> ErrVersionConflict
	staleTask.Title = "Should Fail"
	err = taskRepo.Update(staleTask)
	assert.ErrorIs(t, err, repositories.ErrVersionConflict,
		"Update with stale version should return ErrVersionConflict")
}

// =============================================================================
// Part 1: One-Time Task Completion Tests (TDD)
// =============================================================================

// seedAllFrequencies creates frequency records beyond what SeedLookupData provides
// (Once, Weekly, Monthly). Adds Daily, Bi-Weekly, Quarterly, Yearly, Custom.
func seedAllFrequencies(t *testing.T, db *gorm.DB) {
	t.Helper()

	var existing int64
	db.Model(&models.TaskFrequency{}).Where("name = ?", "Daily").Count(&existing)
	if existing > 0 {
		return
	}

	days1 := 1
	days14 := 14
	days90 := 90
	days365 := 365

	extraFreqs := []models.TaskFrequency{
		{Name: "Daily", Days: &days1, DisplayOrder: 0},
		{Name: "Bi-Weekly", Days: &days14, DisplayOrder: 3},
		{Name: "Quarterly", Days: &days90, DisplayOrder: 5},
		{Name: "Yearly", Days: &days365, DisplayOrder: 6},
		{Name: "Custom", Days: nil, DisplayOrder: 7},
	}
	for i := range extraFreqs {
		err := db.Create(&extraFreqs[i]).Error
		require.NoError(t, err, "failed to seed frequency: %s", extraFreqs[i].Name)
	}
}

func TestTaskService_CreateCompletion_OneTime_NextDueDateBecomesNil(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "One-time Task",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Done",
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	assert.Nil(t, reloaded.NextDueDate, "One-time task NextDueDate should be nil after completion")
}

func TestTaskService_CreateCompletion_OneTime_InProgressBecomesFalse(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "In-progress One-time",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		InProgress:  true,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	assert.False(t, reloaded.InProgress, "One-time task InProgress should be false after completion")
}

func TestTaskService_CreateCompletion_OneTime_CompletionRecordFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	now := time.Now().UTC()
	resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Check all fields",
	}, user.ID, now)
	require.NoError(t, err)

	assert.NotZero(t, resp.Data.ID, "Completion ID should be set")
	assert.Equal(t, task.ID, resp.Data.TaskID, "TaskID should match")
	assert.Equal(t, "Check all fields", resp.Data.Notes, "Notes should match")

	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err)
	assert.Equal(t, user.ID, completion.CompletedByID, "CompletedByID should match user")
}

func TestTaskService_CreateCompletion_OneTime_WithNotesActualCostRating(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	cost := decimal.NewFromFloat(75.50)
	rating := 4
	now := time.Now().UTC()
	resp, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID:     task.ID,
		Notes:      "Detailed notes",
		ActualCost: &cost,
		Rating:     &rating,
	}, user.ID, now)
	require.NoError(t, err)

	assert.Equal(t, "Detailed notes", resp.Data.Notes)
	require.NotNil(t, resp.Data.ActualCost, "ActualCost should be set")
	assert.True(t, resp.Data.ActualCost.Equal(cost), "ActualCost should match")
	require.NotNil(t, resp.Data.Rating, "Rating should be set")
	assert.Equal(t, 4, *resp.Data.Rating)

	// Verify in database
	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err)
	require.NotNil(t, completion.ActualCost)
	assert.True(t, completion.ActualCost.Equal(cost))
	require.NotNil(t, completion.Rating)
	assert.Equal(t, 4, *completion.Rating)
}

func TestTaskService_CreateCompletion_AlreadyCompleted_SecondCompletionCreated(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Double Complete Task",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()

	// First completion
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "First",
	}, user.ID, now)
	require.NoError(t, err)

	var afterFirst models.Task
	db.First(&afterFirst, task.ID)
	assert.Nil(t, afterFirst.NextDueDate, "NextDueDate should be nil after first completion")

	// Second completion on already-completed task
	resp2, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Second",
	}, user.ID, now)
	require.NoError(t, err)
	assert.NotZero(t, resp2.Data.ID, "Second completion should be created")

	// NextDueDate should remain nil
	var afterSecond models.Task
	db.First(&afterSecond, task.ID)
	assert.Nil(t, afterSecond.NextDueDate, "NextDueDate should remain nil after second completion")

	// Two completions should exist
	var count int64
	db.Model(&models.TaskCompletion{}).Where("task_id = ?", task.ID).Count(&count)
	assert.Equal(t, int64(2), count, "Two completions should exist")
}

func TestTaskService_CreateCompletion_WithBackdatedCompletedAt(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	backdated := time.Date(2026, 1, 15, 14, 30, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err := service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Backdated", CompletedAt: &backdated,
	}, user.ID, now)
	require.NoError(t, err)

	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err)
	assert.Equal(t, backdated.Year(), completion.CompletedAt.Year())
	assert.Equal(t, backdated.Month(), completion.CompletedAt.Month())
	assert.Equal(t, backdated.Day(), completion.CompletedAt.Day())
}

func TestTaskService_CreateCompletion_Recurring_BackdatedCompletedAt_NextDueFromThatDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Recurring Backdated",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	backdated := time.Date(2026, 1, 10, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, Notes: "Backdated recurring", CompletedAt: &backdated,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := backdated.AddDate(0, 0, 7)
	assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
		"NextDueDate should be calculated from backdated CompletedAt, not current time")
}

// =============================================================================
// Part 2: Recurring Task Completion Tests - ALL FREQUENCY TYPES (TDD)
// =============================================================================

func TestTaskService_CreateCompletion_Recurring_Daily(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var dailyFreq models.TaskFrequency
	db.Where("name = ?", "Daily").First(&dailyFreq)

	dueDate := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Daily Task",
		FrequencyID: &dailyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()
	completedAt1 := time.Date(2026, 3, 26, 9, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt1,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected1 := completedAt1.AddDate(0, 0, 1)
	assert.Equal(t, expected1.Year(), reloaded.NextDueDate.Year())
	assert.Equal(t, expected1.Month(), reloaded.NextDueDate.Month())
	assert.Equal(t, expected1.Day(), reloaded.NextDueDate.Day(),
		"Daily: NextDueDate should be CompletedAt + 1 day")

	// Complete again
	completedAt2 := time.Date(2026, 3, 27, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt2,
	}, user.ID, now)
	require.NoError(t, err)

	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected2 := completedAt2.AddDate(0, 0, 1)
	assert.Equal(t, expected2.Day(), reloaded.NextDueDate.Day(),
		"Daily: Second completion NextDueDate should be new CompletedAt + 1 day")
}

func TestTaskService_CreateCompletion_Recurring_Weekly_OnTime(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 3, 20, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Weekly OnTime",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 3, 20, 15, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 7)
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
}

func TestTaskService_CreateCompletion_Recurring_Weekly_CompletedLate_NextDueFromCompletedAt(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 3, 20, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Weekly Late",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	// Complete 3 days late
	completedAt := time.Date(2026, 3, 23, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 7) // Mar 30, NOT Mar 27
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
		"NextDueDate should be CompletedAt+7 (Mar 30), not DueDate+7 (Mar 27)")
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
}

func TestTaskService_CreateCompletion_Recurring_BiWeekly(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var biWeeklyFreq models.TaskFrequency
	db.Where("name = ?", "Bi-Weekly").First(&biWeeklyFreq)

	dueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Bi-Weekly Task",
		FrequencyID: &biWeeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 14)
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month(),
		"Bi-Weekly: NextDueDate should be CompletedAt + 14 days")
}

func TestTaskService_CreateCompletion_Recurring_Monthly_Dedicated(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var monthlyFreq models.TaskFrequency
	db.Where("name = ?", "Monthly").First(&monthlyFreq)

	dueDate := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Monthly Dedicated",
		FrequencyID: &monthlyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 2, 5, 12, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 30)
	assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
		"Monthly: NextDueDate should be CompletedAt + 30 days")
}

func TestTaskService_CreateCompletion_Recurring_Quarterly(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var quarterlyFreq models.TaskFrequency
	db.Where("name = ?", "Quarterly").First(&quarterlyFreq)

	dueDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Quarterly Task",
		FrequencyID: &quarterlyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 1, 5, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 90)
	assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
		"Quarterly: NextDueDate should be CompletedAt + 90 days")
}

func TestTaskService_CreateCompletion_Recurring_Yearly(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var yearlyFreq models.TaskFrequency
	db.Where("name = ?", "Yearly").First(&yearlyFreq)

	dueDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Yearly Task",
		FrequencyID: &yearlyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 1, 10, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	expected := completedAt.AddDate(0, 0, 365)
	assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
		"Yearly: NextDueDate should be CompletedAt + 365 days")
}

func TestTaskService_CreateCompletion_Recurring_Custom_Intervals(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var customFreq models.TaskFrequency
	db.Where("name = ?", "Custom").First(&customFreq)

	tests := []struct {
		name         string
		intervalDays int
	}{
		{"Custom_10_days", 10},
		{"Custom_45_days", 45},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			dueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
			customDays := tc.intervalDays
			task := &models.Task{
				ResidenceID:        residence.ID,
				CreatedByID:        user.ID,
				Title:              tc.name,
				FrequencyID:        &customFreq.ID,
				CustomIntervalDays: &customDays,
				DueDate:            &dueDate,
				NextDueDate:        &dueDate,
				IsCancelled:        false,
				IsArchived:         false,
				Version:            1,
			}
			err := db.Create(task).Error
			require.NoError(t, err)

			completedAt := time.Date(2026, 3, 5, 10, 0, 0, 0, time.UTC)
			now := time.Now().UTC()
			_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
				TaskID: task.ID, CompletedAt: &completedAt,
			}, user.ID, now)
			require.NoError(t, err)

			var reloaded models.Task
			db.First(&reloaded, task.ID)
			require.NotNil(t, reloaded.NextDueDate)
			expected := completedAt.AddDate(0, 0, tc.intervalDays)
			assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year())
			assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
			assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(),
				"Custom %d days: NextDueDate should be CompletedAt + %d days", tc.intervalDays, tc.intervalDays)
		})
	}
}

func TestTaskService_CreateCompletion_Recurring_NoDueDate_NextDueFromCompletion(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Recurring No Due Date",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     nil,
		NextDueDate: nil,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	completedAt := time.Date(2026, 3, 26, 10, 0, 0, 0, time.UTC)
	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &completedAt,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate,
		"Recurring task with no original DueDate should have NextDueDate after completion")
	expected := completedAt.AddDate(0, 0, 7)
	assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day())
	assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month())
}

func TestTaskService_CreateCompletion_Recurring_ThreeSequentialCompletions(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "Weekly 3x Complete",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()
	var reloaded models.Task

	// Completion 1: Mar 1 -> NextDueDate = Mar 8
	c1 := time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &c1,
	}, user.ID, now)
	require.NoError(t, err)
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	assert.Equal(t, 8, reloaded.NextDueDate.Day(), "After 1st: NextDueDate should be Mar 8")

	// Completion 2: Mar 8 -> NextDueDate = Mar 15
	c2 := time.Date(2026, 3, 8, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &c2,
	}, user.ID, now)
	require.NoError(t, err)
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	assert.Equal(t, 15, reloaded.NextDueDate.Day(), "After 2nd: NextDueDate should be Mar 15")

	// Completion 3: Mar 15 -> NextDueDate = Mar 22
	c3 := time.Date(2026, 3, 15, 10, 0, 0, 0, time.UTC)
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID, CompletedAt: &c3,
	}, user.ID, now)
	require.NoError(t, err)
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate)
	assert.Equal(t, 22, reloaded.NextDueDate.Day(), "After 3rd: NextDueDate should be Mar 22")

	var count int64
	db.Model(&models.TaskCompletion{}).Where("task_id = ?", task.ID).Count(&count)
	assert.Equal(t, int64(3), count)
}

func TestTaskService_CreateCompletion_Recurring_InProgressResetForNextCycle(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "InProgress Recurring",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		InProgress:  true,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	now := time.Now().UTC()
	_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
		TaskID: task.ID,
	}, user.ID, now)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	assert.False(t, reloaded.InProgress,
		"Recurring task InProgress should be reset to false after completion for next cycle")
	require.NotNil(t, reloaded.NextDueDate)
}

func TestTaskService_CreateCompletion_CompletedFromColumn_Capture(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	t.Run("overdue_task", func(t *testing.T) {
		pastDue := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID: residence.ID,
			CreatedByID: user.ID,
			Title:       "Overdue Column Task",
			DueDate:     &pastDue,
			NextDueDate: &pastDue,
			IsCancelled: false,
			IsArchived:  false,
			Version:     1,
		}
		err := db.Create(task).Error
		require.NoError(t, err)

		now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID,
		}, user.ID, now)
		require.NoError(t, err)

		var completion models.TaskCompletion
		db.Where("task_id = ?", task.ID).First(&completion)
		assert.Equal(t, "overdue_tasks", completion.CompletedFromColumn,
			"CompletedFromColumn should be 'overdue_tasks'")
	})

	t.Run("in_progress_task", func(t *testing.T) {
		futureDue := time.Date(2027, 6, 1, 0, 0, 0, 0, time.UTC)
		task := &models.Task{
			ResidenceID: residence.ID,
			CreatedByID: user.ID,
			Title:       "InProgress Column Task",
			DueDate:     &futureDue,
			NextDueDate: &futureDue,
			InProgress:  true,
			IsCancelled: false,
			IsArchived:  false,
			Version:     1,
		}
		err := db.Create(task).Error
		require.NoError(t, err)

		now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
		_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
			TaskID: task.ID,
		}, user.ID, now)
		require.NoError(t, err)

		var completion models.TaskCompletion
		db.Where("task_id = ?", task.ID).First(&completion)
		assert.Equal(t, "in_progress_tasks", completion.CompletedFromColumn,
			"CompletedFromColumn should be 'in_progress_tasks'")
	})
}

// Table-driven test: all standard frequency types
func TestTaskService_CreateCompletion_AllFrequencyTypes_TableDriven(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	seedAllFrequencies(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	tests := []struct {
		freqName     string
		expectedDays int
	}{
		{"Daily", 1},
		{"Weekly", 7},
		{"Bi-Weekly", 14},
		{"Monthly", 30},
		{"Quarterly", 90},
		{"Yearly", 365},
	}

	for _, tc := range tests {
		t.Run(tc.freqName, func(t *testing.T) {
			var freq models.TaskFrequency
			err := db.Where("name = ?", tc.freqName).First(&freq).Error
			require.NoError(t, err, "Frequency %s should exist", tc.freqName)

			dueDate := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
			task := &models.Task{
				ResidenceID: residence.ID,
				CreatedByID: user.ID,
				Title:       "Freq " + tc.freqName,
				FrequencyID: &freq.ID,
				DueDate:     &dueDate,
				NextDueDate: &dueDate,
				IsCancelled: false,
				IsArchived:  false,
				Version:     1,
			}
			err = db.Create(task).Error
			require.NoError(t, err)

			completedAt := time.Date(2026, 3, 5, 10, 0, 0, 0, time.UTC)
			now := time.Now().UTC()
			_, err = service.CreateCompletion(&requests.CreateTaskCompletionRequest{
				TaskID: task.ID, CompletedAt: &completedAt,
			}, user.ID, now)
			require.NoError(t, err)

			var reloaded models.Task
			db.First(&reloaded, task.ID)
			require.NotNil(t, reloaded.NextDueDate, "%s: NextDueDate should not be nil", tc.freqName)
			expected := completedAt.AddDate(0, 0, tc.expectedDays)
			assert.Equal(t, expected.Year(), reloaded.NextDueDate.Year(), "%s: year", tc.freqName)
			assert.Equal(t, expected.Month(), reloaded.NextDueDate.Month(), "%s: month", tc.freqName)
			assert.Equal(t, expected.Day(), reloaded.NextDueDate.Day(), "%s: day", tc.freqName)
		})
	}
}

// =============================================================================
// Part 3: QuickComplete Tests (TDD)
// =============================================================================

func TestTaskService_QuickComplete_OneTime_ClearsNextDueDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "QuickComplete One-time",
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	err = service.QuickComplete(task.ID, user.ID)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	assert.Nil(t, reloaded.NextDueDate, "One-time QuickComplete should set NextDueDate to nil")

	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err, "Completion record should be created")
	assert.NotZero(t, completion.ID)
}

func TestTaskService_QuickComplete_Recurring_RecalculatesNextDueDate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	var weeklyFreq models.TaskFrequency
	db.Where("name = ?", "Weekly").First(&weeklyFreq)

	dueDate := time.Date(2026, 3, 20, 0, 0, 0, 0, time.UTC)
	task := &models.Task{
		ResidenceID: residence.ID,
		CreatedByID: user.ID,
		Title:       "QuickComplete Recurring",
		FrequencyID: &weeklyFreq.ID,
		DueDate:     &dueDate,
		NextDueDate: &dueDate,
		IsCancelled: false,
		IsArchived:  false,
		Version:     1,
	}
	err := db.Create(task).Error
	require.NoError(t, err)

	err = service.QuickComplete(task.ID, user.ID)
	require.NoError(t, err)

	var reloaded models.Task
	db.First(&reloaded, task.ID)
	require.NotNil(t, reloaded.NextDueDate, "Recurring QuickComplete should set NextDueDate")
	expectedApprox := time.Now().UTC().AddDate(0, 0, 7)
	assert.WithinDuration(t, expectedApprox, *reloaded.NextDueDate, 2*time.Minute,
		"NextDueDate should be ~7 days from now")
}

func TestTaskService_QuickComplete_SetsWidgetNotes(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

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

	err := service.QuickComplete(task.ID, user.ID)
	require.NoError(t, err)

	var completion models.TaskCompletion
	err = db.Where("task_id = ?", task.ID).First(&completion).Error
	require.NoError(t, err)
	assert.Equal(t, "Completed from widget", completion.Notes,
		"QuickComplete should set notes to 'Completed from widget'")
}

func TestTaskService_QuickComplete_NonExistentTask_ReturnsNotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	err := service.QuickComplete(99999, user.ID)
	testutil.AssertAppErrorCode(t, err, http.StatusNotFound)
}

func TestTaskService_QuickComplete_AccessDenied_ReturnsForbidden(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	taskRepo := repositories.NewTaskRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	service := NewTaskService(taskRepo, residenceRepo)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, owner.ID, "Private Task")

	err := service.QuickComplete(task.ID, otherUser.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.task_access_denied")
}
