// Package scopes provides GORM scope functions that mirror the predicates.
// These scopes allow efficient database-level filtering using the same logic
// as the predicates in ../predicates/predicates.go.
//
// IMPORTANT: These scopes must produce the same results as their predicate counterparts.
// Any change to a predicate MUST be reflected in the corresponding scope.
// Tests verify consistency between predicates and scopes.
//
// Each scope includes a comment referencing its predicate counterpart for easy cross-reference.
package scopes

import (
	"time"

	"gorm.io/gorm"
)

// =============================================================================
// STATE SCOPES
// =============================================================================

// ScopeActive filters to tasks that are not cancelled and not archived.
// Active tasks are eligible for display in the kanban board.
//
// Predicate equivalent: IsActive(task)
//
// SQL: is_cancelled = false AND is_archived = false
func ScopeActive(db *gorm.DB) *gorm.DB {
	return db.Where("is_cancelled = ? AND is_archived = ?", false, false)
}

// ScopeCancelled filters to cancelled tasks only.
//
// Predicate equivalent: IsCancelled(task)
//
// SQL: is_cancelled = true
func ScopeCancelled(db *gorm.DB) *gorm.DB {
	return db.Where("is_cancelled = ?", true)
}

// ScopeArchived filters to archived tasks only.
//
// Predicate equivalent: IsArchived(task)
//
// SQL: is_archived = true
func ScopeArchived(db *gorm.DB) *gorm.DB {
	return db.Where("is_archived = ?", true)
}

// ScopeCompleted filters to completed tasks.
//
// A task is completed when NextDueDate is nil AND it has at least one completion.
//
// Predicate equivalent: IsCompleted(task)
//
// SQL: next_due_date IS NULL AND EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)
func ScopeCompleted(db *gorm.DB) *gorm.DB {
	return db.Where(
		"next_due_date IS NULL AND EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)",
	)
}

// ScopeNotCompleted excludes completed tasks.
//
// A task is NOT completed when it either has a NextDueDate OR has no completions.
//
// Predicate equivalent: !IsCompleted(task)
//
// SQL: NOT (next_due_date IS NULL AND EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id))
func ScopeNotCompleted(db *gorm.DB) *gorm.DB {
	return db.Where(
		"NOT (next_due_date IS NULL AND EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id))",
	)
}

// ScopeInProgress filters to tasks marked as in progress.
//
// Predicate equivalent: IsInProgress(task)
//
// SQL: in_progress = true
func ScopeInProgress(db *gorm.DB) *gorm.DB {
	return db.Where("in_progress = ?", true)
}

// ScopeNotInProgress excludes tasks marked as in progress.
//
// Predicate equivalent: !IsInProgress(task)
//
// SQL: in_progress = false
func ScopeNotInProgress(db *gorm.DB) *gorm.DB {
	return db.Where("in_progress = ?", false)
}

// =============================================================================
// DATE SCOPES
// =============================================================================

// ScopeOverdue returns a scope for overdue tasks.
//
// A task is overdue when its effective date (COALESCE(next_due_date, due_date))
// is before the start of the given day, and it's active and not completed.
//
// Note: A task due "today" is NOT overdue. It becomes overdue tomorrow.
//
// Predicate equivalent: IsOverdue(task, now)
//
// IMPORTANT: Due dates are stored as midnight UTC but represent calendar dates.
// We compare dates (YYYY-MM-DD), not timestamps, to ensure timezone correctness.
// A task due "2024-12-16" should only be overdue when the user's date is "2024-12-17" or later.
//
// Uses DATE() function which works in both PostgreSQL and SQLite.
func ScopeOverdue(now time.Time) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		// Extract user's current date as a string for date-only comparison
		// This ensures timezone correctness: the user's "today" determines overdue status
		todayStr := now.Format("2006-01-02")
		return db.Scopes(ScopeActive, ScopeNotCompleted).
			Where("DATE(COALESCE(next_due_date, due_date)) < DATE(?)", todayStr)
	}
}

// ScopeDueSoon returns a scope for tasks due within the threshold.
//
// A task is "due soon" when its effective date is >= start of today AND < start of (today + threshold),
// and it's active and not completed.
//
// Note: Uses day-level comparisons so tasks due "today" are included.
//
// Predicate equivalent: IsDueSoon(task, now, daysThreshold)
//
// IMPORTANT: Uses date comparison (not timestamps) for timezone correctness.
func ScopeDueSoon(now time.Time, daysThreshold int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		// Extract dates as strings for date-only comparison
		todayStr := now.Format("2006-01-02")
		thresholdDate := now.AddDate(0, 0, daysThreshold)
		thresholdStr := thresholdDate.Format("2006-01-02")
		return db.Scopes(ScopeActive, ScopeNotCompleted).
			Where("DATE(COALESCE(next_due_date, due_date)) >= DATE(?)", todayStr).
			Where("DATE(COALESCE(next_due_date, due_date)) < DATE(?)", thresholdStr)
	}
}

// ScopeUpcoming returns a scope for tasks due after the threshold or with no due date.
//
// A task is "upcoming" when its effective date is >= start of (today + threshold) OR is null,
// and it's active and not completed.
//
// Predicate equivalent: IsUpcoming(task, now, daysThreshold)
//
// IMPORTANT: Uses date comparison (not timestamps) for timezone correctness.
func ScopeUpcoming(now time.Time, daysThreshold int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		// Compute threshold date
		thresholdDate := now.AddDate(0, 0, daysThreshold)
		thresholdStr := thresholdDate.Format("2006-01-02")
		return db.Scopes(ScopeActive, ScopeNotCompleted).
			Where(
				"DATE(COALESCE(next_due_date, due_date)) >= DATE(?) OR (next_due_date IS NULL AND due_date IS NULL)",
				thresholdStr,
			)
	}
}

// ScopeDueInRange returns a scope for tasks with effective date in a range.
//
// IMPORTANT: Uses date comparison (not timestamps) for timezone correctness.
func ScopeDueInRange(start, end time.Time) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		startStr := start.Format("2006-01-02")
		endStr := end.Format("2006-01-02")
		return db.
			Where("DATE(COALESCE(next_due_date, due_date)) >= DATE(?)", startStr).
			Where("DATE(COALESCE(next_due_date, due_date)) < DATE(?)", endStr)
	}
}

// ScopeHasDueDate filters to tasks that have an effective due date.
//
// SQL: (next_due_date IS NOT NULL OR due_date IS NOT NULL)
func ScopeHasDueDate(db *gorm.DB) *gorm.DB {
	return db.Where("next_due_date IS NOT NULL OR due_date IS NOT NULL")
}

// ScopeNoDueDate filters to tasks that have no effective due date.
//
// SQL: next_due_date IS NULL AND due_date IS NULL
func ScopeNoDueDate(db *gorm.DB) *gorm.DB {
	return db.Where("next_due_date IS NULL AND due_date IS NULL")
}

// =============================================================================
// FILTER SCOPES
// =============================================================================

// ScopeForResidence filters tasks by a single residence ID.
//
// SQL: residence_id = ?
func ScopeForResidence(residenceID uint) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Where("residence_id = ?", residenceID)
	}
}

// ScopeForResidences filters tasks by multiple residence IDs.
//
// SQL: residence_id IN (?)
func ScopeForResidences(residenceIDs []uint) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if len(residenceIDs) == 0 {
			// Return empty result if no residence IDs provided
			return db.Where("1 = 0")
		}
		return db.Where("residence_id IN ?", residenceIDs)
	}
}

// ScopeHasCompletions filters to tasks that have at least one completion.
//
// Predicate equivalent: HasCompletions(task)
//
// SQL: EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)
func ScopeHasCompletions(db *gorm.DB) *gorm.DB {
	return db.Where(
		"EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)",
	)
}

// ScopeNoCompletions filters to tasks that have no completions.
//
// Predicate equivalent: !HasCompletions(task)
//
// SQL: NOT EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)
func ScopeNoCompletions(db *gorm.DB) *gorm.DB {
	return db.Where(
		"NOT EXISTS (SELECT 1 FROM task_taskcompletion tc WHERE tc.task_id = task_task.id)",
	)
}

// =============================================================================
// ORDERING
// =============================================================================

// ScopeOrderByDueDate orders tasks by effective due date ascending, nulls last.
//
// SQL: ORDER BY COALESCE(next_due_date, due_date) ASC NULLS LAST
func ScopeOrderByDueDate(db *gorm.DB) *gorm.DB {
	return db.Order("COALESCE(next_due_date, due_date) ASC NULLS LAST")
}

// ScopeOrderByPriority orders tasks by priority level descending (urgent first).
//
// SQL: ORDER BY priority_id DESC
func ScopeOrderByPriority(db *gorm.DB) *gorm.DB {
	return db.Order("priority_id DESC")
}

// ScopeOrderByCreatedAt orders tasks by creation date descending (newest first).
//
// SQL: ORDER BY created_at DESC
func ScopeOrderByCreatedAt(db *gorm.DB) *gorm.DB {
	return db.Order("created_at DESC")
}

// ScopeKanbanOrder applies the standard kanban ordering.
//
// SQL: ORDER BY COALESCE(next_due_date, due_date) ASC NULLS LAST, priority_id DESC, created_at DESC
func ScopeKanbanOrder(db *gorm.DB) *gorm.DB {
	return db.Order("COALESCE(next_due_date, due_date) ASC NULLS LAST, priority_id DESC, created_at DESC")
}
