// Package categorization implements the Chain of Responsibility pattern for
// determining which kanban column a task belongs to.
//
// The chain evaluates tasks in a specific priority order, with each handler
// checking if the task matches its criteria. If a handler matches, it returns
// the column name; otherwise, it passes to the next handler in the chain.
//
// IMPORTANT: This package uses predicates from the parent task package as the
// single source of truth for task logic. Do NOT duplicate logic here.
package categorization

import (
	"time"

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

// KanbanColumn represents the possible kanban column names
type KanbanColumn string

const (
	ColumnOverdue    KanbanColumn = "overdue_tasks"
	ColumnDueSoon    KanbanColumn = "due_soon_tasks"
	ColumnUpcoming   KanbanColumn = "upcoming_tasks"
	ColumnInProgress KanbanColumn = "in_progress_tasks"
	ColumnCompleted  KanbanColumn = "completed_tasks"
	ColumnCancelled  KanbanColumn = "cancelled_tasks"
)

// String returns the string representation of the column
func (c KanbanColumn) String() string {
	return string(c)
}

// Context holds the data needed to categorize a task
type Context struct {
	Task          *models.Task
	Now           time.Time // Always normalized to start of day
	DaysThreshold int
}

// startOfDay normalizes a time to the start of that day (midnight)
func startOfDay(t time.Time) time.Time {
	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}

// normalizeToTimezone converts a date to start of day in a specific timezone.
// This is needed because task due dates are stored as midnight UTC, but we need
// to compare them as calendar dates in the user's timezone.
//
// Example: A task due Dec 17 is stored as 2025-12-17 00:00:00 UTC.
// For a user in Tokyo (UTC+9), we need to compare against Dec 17 in Tokyo time,
// not against the UTC timestamp.
func normalizeToTimezone(t time.Time, loc *time.Location) time.Time {
	// Extract the calendar date (year, month, day) from the time
	// regardless of its original timezone, then create midnight in target timezone
	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
}

// NewContext creates a new categorization context with sensible defaults.
// Uses UTC time, normalized to start of day.
// For timezone-aware categorization, use NewContextWithTime.
func NewContext(t *models.Task, daysThreshold int) *Context {
	if daysThreshold <= 0 {
		daysThreshold = 30
	}
	return &Context{
		Task:          t,
		Now:           startOfDay(time.Now().UTC()),
		DaysThreshold: daysThreshold,
	}
}

// NewContextWithTime creates a new categorization context with a specific time.
// The time is normalized to start of day for consistent date comparisons.
// Use this when you need timezone-aware categorization - pass the current time
// in the user's timezone (it will be normalized to start of day).
func NewContextWithTime(t *models.Task, daysThreshold int, now time.Time) *Context {
	if daysThreshold <= 0 {
		daysThreshold = 30
	}
	return &Context{
		Task:          t,
		Now:           startOfDay(now),
		DaysThreshold: daysThreshold,
	}
}

// ThresholdDate returns the date threshold for "due soon" categorization
// (start of day + daysThreshold days)
func (c *Context) ThresholdDate() time.Time {
	return c.Now.AddDate(0, 0, c.DaysThreshold)
}

// Handler defines the interface for task categorization handlers
type Handler interface {
	// SetNext sets the next handler in the chain
	SetNext(handler Handler) Handler

	// Handle processes the task and returns the column name if matched,
	// or delegates to the next handler
	Handle(ctx *Context) KanbanColumn
}

// BaseHandler provides default chaining behavior
type BaseHandler struct {
	next Handler
}

// SetNext sets the next handler and returns it for fluent chaining
func (h *BaseHandler) SetNext(handler Handler) Handler {
	h.next = handler
	return handler
}

// HandleNext delegates to the next handler or returns default
func (h *BaseHandler) HandleNext(ctx *Context) KanbanColumn {
	if h.next != nil {
		return h.next.Handle(ctx)
	}
	return ColumnUpcoming // Default fallback
}

// === Concrete Handlers ===
// Each handler uses predicates from the task package as the source of truth.

// CancelledHandler checks if the task is cancelled
// Priority: 1 (highest - checked first)
type CancelledHandler struct {
	BaseHandler
}

func (h *CancelledHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.IsCancelled
	if predicates.IsCancelled(ctx.Task) {
		return ColumnCancelled
	}
	return h.HandleNext(ctx)
}

// ArchivedHandler checks if the task is archived
// Priority: 2 - Archived tasks go to cancelled column (both are "inactive" states)
type ArchivedHandler struct {
	BaseHandler
}

func (h *ArchivedHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.IsArchived
	// Archived tasks are placed in the cancelled column since both represent
	// "inactive" task states that are removed from active workflow
	if predicates.IsArchived(ctx.Task) {
		return ColumnCancelled
	}
	return h.HandleNext(ctx)
}

// CompletedHandler checks if the task is completed (one-time task with completions and no next due date)
// Priority: 3
type CompletedHandler struct {
	BaseHandler
}

func (h *CompletedHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.IsCompleted
	// A task is completed if NextDueDate is nil AND has at least one completion
	if predicates.IsCompleted(ctx.Task) {
		return ColumnCompleted
	}
	return h.HandleNext(ctx)
}

// InProgressHandler checks if the task status is "In Progress"
// Priority: 4
type InProgressHandler struct {
	BaseHandler
}

func (h *InProgressHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.IsInProgress
	if predicates.IsInProgress(ctx.Task) {
		return ColumnInProgress
	}
	return h.HandleNext(ctx)
}

// OverdueHandler checks if the task is overdue based on NextDueDate or DueDate
// Priority: 5
type OverdueHandler struct {
	BaseHandler
}

func (h *OverdueHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.EffectiveDate
	// Note: We don't use predicates.IsOverdue here because the chain has already
	// filtered out cancelled and completed tasks. We just need the date check.
	effectiveDate := predicates.EffectiveDate(ctx.Task)
	if effectiveDate == nil {
		return h.HandleNext(ctx)
	}

	// Normalize the effective date to the same timezone as ctx.Now for proper
	// calendar date comparison. Task dates are stored as UTC but represent
	// calendar dates (YYYY-MM-DD), not timestamps.
	normalizedEffective := normalizeToTimezone(*effectiveDate, ctx.Now.Location())
	if normalizedEffective.Before(ctx.Now) {
		return ColumnOverdue
	}
	return h.HandleNext(ctx)
}

// DueSoonHandler checks if the task is due within the threshold period
// Priority: 6
type DueSoonHandler struct {
	BaseHandler
}

func (h *DueSoonHandler) Handle(ctx *Context) KanbanColumn {
	// Uses predicate: predicates.EffectiveDate
	effectiveDate := predicates.EffectiveDate(ctx.Task)
	if effectiveDate == nil {
		return h.HandleNext(ctx)
	}

	// Normalize the effective date to the same timezone as ctx.Now for proper
	// calendar date comparison. Task dates are stored as UTC but represent
	// calendar dates (YYYY-MM-DD), not timestamps.
	normalizedEffective := normalizeToTimezone(*effectiveDate, ctx.Now.Location())
	threshold := ctx.ThresholdDate()

	if normalizedEffective.Before(threshold) {
		return ColumnDueSoon
	}
	return h.HandleNext(ctx)
}

// UpcomingHandler is the final handler that catches all remaining tasks
// Priority: 7 (lowest - default)
type UpcomingHandler struct {
	BaseHandler
}

func (h *UpcomingHandler) Handle(ctx *Context) KanbanColumn {
	// This is the default catch-all for tasks that:
	// - Are not cancelled, completed, or in progress
	// - Are not overdue or due soon
	// - Have a due date far in the future OR no due date at all
	return ColumnUpcoming
}

// === Chain Builder ===

// Chain manages the categorization chain
type Chain struct {
	head Handler
}

// NewChain creates a new categorization chain with handlers in priority order
func NewChain() *Chain {
	// Build the chain in priority order (first handler has highest priority)
	cancelled := &CancelledHandler{}
	archived := &ArchivedHandler{}
	completed := &CompletedHandler{}
	inProgress := &InProgressHandler{}
	overdue := &OverdueHandler{}
	dueSoon := &DueSoonHandler{}
	upcoming := &UpcomingHandler{}

	// Chain them together: cancelled -> archived -> completed -> inProgress -> overdue -> dueSoon -> upcoming
	cancelled.SetNext(archived).
		SetNext(completed).
		SetNext(inProgress).
		SetNext(overdue).
		SetNext(dueSoon).
		SetNext(upcoming)

	return &Chain{head: cancelled}
}

// Categorize determines which kanban column a task belongs to.
// Uses UTC time. For timezone-aware categorization, use CategorizeWithTime.
func (c *Chain) Categorize(t *models.Task, daysThreshold int) KanbanColumn {
	ctx := NewContext(t, daysThreshold)
	return c.head.Handle(ctx)
}

// CategorizeWithTime determines which kanban column a task belongs to using a specific time.
// Use this when you need timezone-aware categorization - pass the start of day
// in the user's timezone.
func (c *Chain) CategorizeWithTime(t *models.Task, daysThreshold int, now time.Time) KanbanColumn {
	ctx := NewContextWithTime(t, daysThreshold, now)
	return c.head.Handle(ctx)
}

// CategorizeWithContext uses a pre-built context for categorization
func (c *Chain) CategorizeWithContext(ctx *Context) KanbanColumn {
	return c.head.Handle(ctx)
}

// === Convenience Functions ===

// defaultChain is a singleton chain instance for convenience
var defaultChain = NewChain()

// DetermineKanbanColumn is a convenience function that uses the default chain.
// Uses UTC time. For timezone-aware categorization, use DetermineKanbanColumnWithTime.
func DetermineKanbanColumn(t *models.Task, daysThreshold int) string {
	return defaultChain.Categorize(t, daysThreshold).String()
}

// DetermineKanbanColumnWithTime is a convenience function that uses a specific time.
// Use this when you need timezone-aware categorization.
func DetermineKanbanColumnWithTime(t *models.Task, daysThreshold int, now time.Time) string {
	return defaultChain.CategorizeWithTime(t, daysThreshold, now).String()
}

// CategorizeTask is an alias for DetermineKanbanColumn with a more descriptive name.
// Uses UTC time. For timezone-aware categorization, use CategorizeTaskWithTime.
func CategorizeTask(t *models.Task, daysThreshold int) KanbanColumn {
	return defaultChain.Categorize(t, daysThreshold)
}

// CategorizeTaskWithTime categorizes a task using a specific time.
// Use this when you need timezone-aware categorization - pass the start of day
// in the user's timezone.
func CategorizeTaskWithTime(t *models.Task, daysThreshold int, now time.Time) KanbanColumn {
	return defaultChain.CategorizeWithTime(t, daysThreshold, now)
}

// CategorizeTasksIntoColumns categorizes multiple tasks into their respective columns.
// Uses UTC time. For timezone-aware categorization, use CategorizeTasksIntoColumnsWithTime.
func CategorizeTasksIntoColumns(tasks []models.Task, daysThreshold int) map[KanbanColumn][]models.Task {
	return CategorizeTasksIntoColumnsWithTime(tasks, daysThreshold, time.Now().UTC())
}

// CategorizeTasksIntoColumnsWithTime categorizes multiple tasks using a specific time.
// Use this when you need timezone-aware categorization - pass the start of day
// in the user's timezone.
func CategorizeTasksIntoColumnsWithTime(tasks []models.Task, daysThreshold int, now time.Time) map[KanbanColumn][]models.Task {
	result := make(map[KanbanColumn][]models.Task)

	// Initialize all columns with empty slices
	for _, col := range []KanbanColumn{
		ColumnOverdue, ColumnDueSoon, ColumnUpcoming,
		ColumnInProgress, ColumnCompleted, ColumnCancelled,
	} {
		result[col] = make([]models.Task, 0)
	}

	// Categorize each task
	chain := NewChain()
	for _, t := range tasks {
		column := chain.CategorizeWithTime(&t, daysThreshold, now)
		result[column] = append(result[column], t)
	}

	return result
}
