package storage

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/denisovdennis/autohero/internal/model"
)

// DailyTaskStore handles daily/weekly task operations against PostgreSQL.
type DailyTaskStore struct {
	pool *pgxpool.Pool
}

// NewDailyTaskStore creates a new DailyTaskStore backed by the given connection pool.
func NewDailyTaskStore(pool *pgxpool.Pool) *DailyTaskStore {
	return &DailyTaskStore{pool: pool}
}

// periodStart returns the start of the current period for a given period type.
// Daily: start of today (UTC). Weekly: start of this Monday (UTC).
func periodStart(now time.Time, period string) time.Time {
	t := now.UTC()
	today := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
	if period == "weekly" {
		// Go back to Monday.
		weekday := int(today.Weekday())
		if weekday == 0 {
			weekday = 7 // Sunday -> 7
		}
		return today.AddDate(0, 0, -(weekday - 1))
	}
	return today
}

// EnsureHeroTasks creates task rows for the current period if they don't already exist.
// Called lazily when the hero checks their tasks.
func (s *DailyTaskStore) EnsureHeroTasks(ctx context.Context, heroID int64, now time.Time) error {
	// Load all task definitions.
	rows, err := s.pool.Query(ctx, `SELECT id, period FROM daily_tasks`)
	if err != nil {
		return fmt.Errorf("ensure hero tasks list: %w", err)
	}
	defer rows.Close()

	type taskDef struct {
		id     string
		period string
	}
	var tasks []taskDef
	for rows.Next() {
		var t taskDef
		if err := rows.Scan(&t.id, &t.period); err != nil {
			return fmt.Errorf("scan task def: %w", err)
		}
		tasks = append(tasks, t)
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("task defs rows: %w", err)
	}

	for _, t := range tasks {
		ps := periodStart(now, t.period)
		_, err := s.pool.Exec(ctx, `
			INSERT INTO hero_daily_tasks (hero_id, task_id, progress, completed, claimed, period_start)
			VALUES ($1, $2, 0, false, false, $3)
			ON CONFLICT DO NOTHING
		`, heroID, t.id, ps)
		if err != nil {
			return fmt.Errorf("ensure task %s: %w", t.id, err)
		}
	}
	return nil
}

// ListHeroTasks returns current daily and weekly tasks with progress for a hero.
// Only returns tasks for the current period (daily = today, weekly = this week).
func (s *DailyTaskStore) ListHeroTasks(ctx context.Context, heroID int64) ([]model.HeroDailyTask, error) {
	now := time.Now().UTC()
	dailyStart := periodStart(now, "daily")
	weeklyStart := periodStart(now, "weekly")

	rows, err := s.pool.Query(ctx, `
		SELECT hdt.hero_id, hdt.task_id, hdt.progress, hdt.completed, hdt.claimed, hdt.period_start,
		       dt.id, dt.title, dt.description, dt.objective_type, dt.objective_count,
		       dt.reward_type, dt.reward_amount, dt.period
		FROM hero_daily_tasks hdt
		JOIN daily_tasks dt ON hdt.task_id = dt.id
		WHERE hdt.hero_id = $1
		  AND (
		    (dt.period = 'daily' AND hdt.period_start = $2)
		    OR
		    (dt.period = 'weekly' AND hdt.period_start = $3)
		  )
		ORDER BY dt.period ASC, dt.id ASC
	`, heroID, dailyStart, weeklyStart)
	if err != nil {
		return nil, fmt.Errorf("list hero tasks: %w", err)
	}
	defer rows.Close()

	var tasks []model.HeroDailyTask
	for rows.Next() {
		var ht model.HeroDailyTask
		var dt model.DailyTask
		if err := rows.Scan(
			&ht.HeroID, &ht.TaskID, &ht.Progress, &ht.Completed, &ht.Claimed, &ht.PeriodStart,
			&dt.ID, &dt.Title, &dt.Description, &dt.ObjectiveType, &dt.ObjectiveCount,
			&dt.RewardType, &dt.RewardAmount, &dt.Period,
		); err != nil {
			return nil, fmt.Errorf("scan hero task: %w", err)
		}
		ht.Task = &dt
		tasks = append(tasks, ht)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("hero tasks rows: %w", err)
	}
	if tasks == nil {
		tasks = []model.HeroDailyTask{}
	}
	return tasks, nil
}

// IncrementTaskProgress increments progress for all matching uncompleted tasks
// in the current period. Automatically marks tasks as completed when the objective
// count is reached.
func (s *DailyTaskStore) IncrementTaskProgress(ctx context.Context, heroID int64, objectiveType string, delta int) error {
	if delta <= 0 {
		return nil
	}

	now := time.Now().UTC()
	dailyStart := periodStart(now, "daily")
	weeklyStart := periodStart(now, "weekly")

	_, err := s.pool.Exec(ctx, `
		UPDATE hero_daily_tasks hdt
		SET progress = LEAST(hdt.progress + $3, dt.objective_count),
		    completed = CASE WHEN hdt.progress + $3 >= dt.objective_count THEN true ELSE hdt.completed END
		FROM daily_tasks dt
		WHERE hdt.task_id = dt.id
		  AND hdt.hero_id = $1
		  AND hdt.completed = false
		  AND dt.objective_type = $2
		  AND (
		    (dt.period = 'daily' AND hdt.period_start = $4)
		    OR
		    (dt.period = 'weekly' AND hdt.period_start = $5)
		  )
	`, heroID, objectiveType, delta, dailyStart, weeklyStart)
	if err != nil {
		return fmt.Errorf("increment task progress: %w", err)
	}
	return nil
}

// ClaimTask marks a completed task as claimed and returns the reward.
// Returns an error if the task is not completed or already claimed.
func (s *DailyTaskStore) ClaimTask(ctx context.Context, heroID int64, taskID string) (*model.DailyTaskReward, error) {
	now := time.Now().UTC()
	dailyStart := periodStart(now, "daily")
	weeklyStart := periodStart(now, "weekly")

	var rewardType string
	var rewardAmount int

	err := s.pool.QueryRow(ctx, `
		UPDATE hero_daily_tasks hdt
		SET claimed = true
		FROM daily_tasks dt
		WHERE hdt.task_id = dt.id
		  AND hdt.hero_id = $1
		  AND hdt.task_id = $2
		  AND hdt.completed = true
		  AND hdt.claimed = false
		  AND (
		    (dt.period = 'daily' AND hdt.period_start = $3)
		    OR
		    (dt.period = 'weekly' AND hdt.period_start = $4)
		  )
		RETURNING dt.reward_type, dt.reward_amount
	`, heroID, taskID, dailyStart, weeklyStart).Scan(&rewardType, &rewardAmount)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, fmt.Errorf("task not found, not completed, or already claimed")
		}
		return nil, fmt.Errorf("claim task: %w", err)
	}

	return &model.DailyTaskReward{
		RewardType:   rewardType,
		RewardAmount: rewardAmount,
	}, nil
}
