package storage

import (
	"context"
	"fmt"
	"time"

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

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

// AchievementStore handles achievement CRUD operations against PostgreSQL.
type AchievementStore struct {
	pool *pgxpool.Pool
}

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

// ListAchievements returns all achievement definitions.
func (s *AchievementStore) ListAchievements(ctx context.Context) ([]model.Achievement, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, title, description, condition_type, condition_value, reward_type, reward_amount
		FROM achievements
		ORDER BY condition_value ASC
	`)
	if err != nil {
		return nil, fmt.Errorf("list achievements: %w", err)
	}
	defer rows.Close()

	var achievements []model.Achievement
	for rows.Next() {
		var a model.Achievement
		if err := rows.Scan(&a.ID, &a.Title, &a.Description, &a.ConditionType, &a.ConditionValue, &a.RewardType, &a.RewardAmount); err != nil {
			return nil, fmt.Errorf("scan achievement: %w", err)
		}
		achievements = append(achievements, a)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("list achievements rows: %w", err)
	}
	if achievements == nil {
		achievements = []model.Achievement{}
	}
	return achievements, nil
}

// GetHeroAchievements returns all unlocked achievements for a hero.
func (s *AchievementStore) GetHeroAchievements(ctx context.Context, heroID int64) ([]model.HeroAchievement, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT ha.hero_id, ha.achievement_id, ha.unlocked_at,
		       a.id, a.title, a.description, a.condition_type, a.condition_value, a.reward_type, a.reward_amount
		FROM hero_achievements ha
		JOIN achievements a ON ha.achievement_id = a.id
		WHERE ha.hero_id = $1
		ORDER BY ha.unlocked_at ASC
	`, heroID)
	if err != nil {
		return nil, fmt.Errorf("get hero achievements: %w", err)
	}
	defer rows.Close()

	var has []model.HeroAchievement
	for rows.Next() {
		var ha model.HeroAchievement
		var a model.Achievement
		if err := rows.Scan(
			&ha.HeroID, &ha.AchievementID, &ha.UnlockedAt,
			&a.ID, &a.Title, &a.Description, &a.ConditionType, &a.ConditionValue, &a.RewardType, &a.RewardAmount,
		); err != nil {
			return nil, fmt.Errorf("scan hero achievement: %w", err)
		}
		ha.Achievement = &a
		has = append(has, ha)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("hero achievements rows: %w", err)
	}
	if has == nil {
		has = []model.HeroAchievement{}
	}
	return has, nil
}

// UnlockAchievement records that a hero has unlocked an achievement.
// Uses ON CONFLICT DO NOTHING to be idempotent.
func (s *AchievementStore) UnlockAchievement(ctx context.Context, heroID int64, achievementID string) error {
	_, err := s.pool.Exec(ctx, `
		INSERT INTO hero_achievements (hero_id, achievement_id, unlocked_at)
		VALUES ($1, $2, now())
		ON CONFLICT DO NOTHING
	`, heroID, achievementID)
	if err != nil {
		return fmt.Errorf("unlock achievement: %w", err)
	}
	return nil
}

// CheckAndUnlock checks all achievement conditions against the hero's current stats,
// unlocks any newly met achievements, and returns the list of newly unlocked ones.
func (s *AchievementStore) CheckAndUnlock(ctx context.Context, hero *model.Hero) ([]model.Achievement, error) {
	allAchievements, err := s.ListAchievements(ctx)
	if err != nil {
		return nil, fmt.Errorf("check and unlock: %w", err)
	}

	// Load already-unlocked IDs for fast lookup.
	unlockedRows, err := s.pool.Query(ctx, `
		SELECT achievement_id FROM hero_achievements WHERE hero_id = $1
	`, hero.ID)
	if err != nil {
		return nil, fmt.Errorf("check unlocked: %w", err)
	}
	defer unlockedRows.Close()

	unlocked := make(map[string]bool)
	for unlockedRows.Next() {
		var id string
		if err := unlockedRows.Scan(&id); err != nil {
			return nil, fmt.Errorf("scan unlocked id: %w", err)
		}
		unlocked[id] = true
	}
	if err := unlockedRows.Err(); err != nil {
		return nil, fmt.Errorf("unlocked rows: %w", err)
	}

	var newlyUnlocked []model.Achievement
	now := time.Now()

	for i := range allAchievements {
		a := &allAchievements[i]
		if unlocked[a.ID] {
			continue
		}
		if !model.CheckAchievementCondition(a, hero) {
			continue
		}

		// Unlock it.
		_, err := s.pool.Exec(ctx, `
			INSERT INTO hero_achievements (hero_id, achievement_id, unlocked_at)
			VALUES ($1, $2, $3)
			ON CONFLICT DO NOTHING
		`, hero.ID, a.ID, now)
		if err != nil {
			return nil, fmt.Errorf("unlock achievement %s: %w", a.ID, err)
		}
		newlyUnlocked = append(newlyUnlocked, *a)
	}

	return newlyUnlocked, nil
}
