package storage

import (
	"context"
	"fmt"
	"strings"

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

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

type ContentStore struct {
	pool *pgxpool.Pool
}

func NewContentStore(pool *pgxpool.Pool) *ContentStore {
	return &ContentStore{pool: pool}
}

func (s *ContentStore) LoadEnemyTemplates(ctx context.Context) ([]model.Enemy, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, type, archetype, biome, name, hp, max_hp, attack, defense, speed, crit_chance,
		       min_level, max_level, base_level, level_variance_pct, max_hero_level_diff,
		       hp_per_level, attack_per_level, defense_per_level, xp_per_level, gold_per_level,
		       xp_reward, gold_reward, special_abilities, is_elite
		FROM enemies
	`)
	if err != nil {
		return nil, fmt.Errorf("load enemies from db: %w", err)
	}
	defer rows.Close()

	var out []model.Enemy
	for rows.Next() {
		var (
			e                model.Enemy
			slug             string
			specialAbilities []string
		)
		if err := rows.Scan(
			&e.ID, &slug, &e.Archetype, &e.Biome, &e.Name, &e.HP, &e.MaxHP, &e.Attack, &e.Defense, &e.Speed, &e.CritChance,
			&e.MinLevel, &e.MaxLevel, &e.BaseLevel, &e.LevelVariance, &e.MaxHeroLevelDiff,
			&e.HPPerLevel, &e.AttackPerLevel, &e.DefensePerLevel, &e.XPPerLevel, &e.GoldPerLevel,
			&e.XPReward, &e.GoldReward, &specialAbilities, &e.IsElite,
		); err != nil {
			return nil, fmt.Errorf("scan enemy row: %w", err)
		}
		e.Slug = slug
		e.SpecialAbilities = make([]model.SpecialAbility, 0, len(specialAbilities))
		for _, a := range specialAbilities {
			e.SpecialAbilities = append(e.SpecialAbilities, model.SpecialAbility(a))
		}
		out = append(out, e)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("enemy rows: %w", err)
	}
	return out, nil
}

// EnemyRow is one row from the enemies table (admin / tooling).
type EnemyRow struct {
	ID               int64    `json:"id"`
	Type             string   `json:"type"` // slug
	Archetype        string   `json:"archetype"`
	Biome            string   `json:"biome"`
	Name             string   `json:"name"`
	HP               int      `json:"hp"`
	MaxHP            int      `json:"maxHp"`
	Attack           int      `json:"attack"`
	Defense          int      `json:"defense"`
	Speed            float64  `json:"speed"`
	CritChance       float64  `json:"critChance"`
	MinLevel         int      `json:"minLevel"`
	MaxLevel         int      `json:"maxLevel"`
	BaseLevel        int      `json:"baseLevel"`
	LevelVariance    float64  `json:"levelVariance"`
	MaxHeroLevelDiff int      `json:"maxHeroLevelDiff"`
	HPPerLevel       float64  `json:"hpPerLevel"`
	AttackPerLevel   float64  `json:"attackPerLevel"`
	DefensePerLevel  float64  `json:"defensePerLevel"`
	XPPerLevel       float64  `json:"xpPerLevel"`
	GoldPerLevel     float64  `json:"goldPerLevel"`
	XPReward         int64    `json:"xpReward"`
	GoldReward       int64    `json:"goldReward"`
	SpecialAbilities []string `json:"specialAbilities"`
	IsElite          bool     `json:"isElite"`
}

// ListEnemyRows returns all enemy templates ordered by min_level, type.
func (s *ContentStore) ListEnemyRows(ctx context.Context) ([]EnemyRow, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, type, archetype, biome, name, hp, max_hp, attack, defense, speed, crit_chance,
		       min_level, max_level, base_level, level_variance_pct, max_hero_level_diff,
		       hp_per_level, attack_per_level, defense_per_level, xp_per_level, gold_per_level,
		       xp_reward, gold_reward, special_abilities, is_elite
		FROM enemies
		ORDER BY min_level, archetype, type
	`)
	if err != nil {
		return nil, fmt.Errorf("list enemies: %w", err)
	}
	defer rows.Close()

	var out []EnemyRow
	for rows.Next() {
		var r EnemyRow
		if err := rows.Scan(
			&r.ID, &r.Type, &r.Archetype, &r.Biome, &r.Name, &r.HP, &r.MaxHP, &r.Attack, &r.Defense, &r.Speed, &r.CritChance,
			&r.MinLevel, &r.MaxLevel, &r.BaseLevel, &r.LevelVariance, &r.MaxHeroLevelDiff,
			&r.HPPerLevel, &r.AttackPerLevel, &r.DefensePerLevel, &r.XPPerLevel, &r.GoldPerLevel,
			&r.XPReward, &r.GoldReward, &r.SpecialAbilities, &r.IsElite,
		); err != nil {
			return nil, fmt.Errorf("scan enemy row: %w", err)
		}
		out = append(out, r)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return out, nil
}

// UpdateEnemyByType persists one template and sets hp = max_hp = MaxHP from e.
func (s *ContentStore) UpdateEnemyByType(ctx context.Context, typ string, e model.Enemy) error {
	abilities := make([]string, 0, len(e.SpecialAbilities))
	for _, a := range e.SpecialAbilities {
		abilities = append(abilities, string(a))
	}
	tag, err := s.pool.Exec(ctx, `
		UPDATE enemies SET
			archetype = $2,
			biome = $3,
			name = $4,
			hp = $5,
			max_hp = $6,
			attack = $7,
			defense = $8,
			speed = $9,
			crit_chance = $10,
			min_level = $11,
			max_level = $12,
			base_level = $13,
			level_variance_pct = $14,
			max_hero_level_diff = $15,
			hp_per_level = $16,
			attack_per_level = $17,
			defense_per_level = $18,
			xp_per_level = $19,
			gold_per_level = $20,
			xp_reward = $21,
			gold_reward = $22,
			special_abilities = $23::text[],
			is_elite = $24
		WHERE type = $1
	`, typ, e.Archetype, e.Biome, e.Name, e.MaxHP, e.MaxHP, e.Attack, e.Defense, e.Speed, e.CritChance,
		e.MinLevel, e.MaxLevel, e.BaseLevel, e.LevelVariance, e.MaxHeroLevelDiff,
		e.HPPerLevel, e.AttackPerLevel, e.DefensePerLevel, e.XPPerLevel, e.GoldPerLevel,
		e.XPReward, e.GoldReward, abilities, e.IsElite)
	if err != nil {
		return fmt.Errorf("update enemy: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("no enemy row with type %q", typ)
	}
	return nil
}

func normalizeEquipmentSlot(raw string) model.EquipmentSlot {
	v := strings.TrimSpace(strings.ToLower(raw))
	v = strings.TrimPrefix(v, "gear.slot.")
	switch v {
	case "weapon", "mainhand", "main_hand":
		return model.SlotMainHand
	case "armor", "chest":
		return model.SlotChest
	case "head":
		return model.SlotHead
	case "feet":
		return model.SlotFeet
	case "neck":
		return model.SlotNeck
	case "hands":
		return model.SlotHands
	case "legs":
		return model.SlotLegs
	case "cloak":
		return model.SlotCloak
	case "finger", "ring":
		return model.SlotFinger
	case "wrist":
		return model.SlotWrist
	default:
		return model.EquipmentSlot(v)
	}
}

func (s *ContentStore) LoadGearFamilies(ctx context.Context) ([]model.GearFamily, error) {
	out := make([]model.GearFamily, 0, 128)

	// One catalog row per item name: pick lowest id (template rows predate player-owned gear ids).
	weaponRows, err := s.pool.Query(ctx, `
		SELECT DISTINCT ON (name)
			name, subtype, base_primary, stat_type, speed_modifier, crit_chance, agility_bonus, set_name, special_effect, form_id
		FROM gear
		WHERE slot = 'main_hand'
		ORDER BY name, id
	`)
	if err != nil {
		return nil, fmt.Errorf("load main_hand gear templates from db: %w", err)
	}
	for weaponRows.Next() {
		var name, subtype, statType, setName, special, formID string
		var basePrimary, agi int
		var speed, crit float64
		if err := weaponRows.Scan(&name, &subtype, &basePrimary, &statType, &speed, &crit, &agi, &setName, &special, &formID); err != nil {
			weaponRows.Close()
			return nil, fmt.Errorf("scan main_hand gear row: %w", err)
		}
		if strings.TrimSpace(formID) == "" {
			formID = "gear.form.main_hand." + subtype
		}
		out = append(out, model.GearFamily{
			Slot:          model.SlotMainHand,
			FormID:        formID,
			Name:          name,
			Subtype:       subtype,
			BasePrimary:   basePrimary,
			StatType:      statType,
			SpeedModifier: speed,
			BaseCrit:      crit,
			AgilityBonus:  agi,
			SetName:       setName,
			SpecialEffect: special,
		})
	}
	weaponRows.Close()

	armorRows, err := s.pool.Query(ctx, `
		SELECT DISTINCT ON (name)
			name, subtype, base_primary, stat_type, speed_modifier, crit_chance, agility_bonus, set_name, special_effect, form_id
		FROM gear
		WHERE slot = 'chest'
		ORDER BY name, id
	`)
	if err != nil {
		return nil, fmt.Errorf("load chest gear templates from db: %w", err)
	}
	for armorRows.Next() {
		var name, subtype, statType, setName, special, formID string
		var basePrimary, agi int
		var speed, crit float64
		if err := armorRows.Scan(&name, &subtype, &basePrimary, &statType, &speed, &crit, &agi, &setName, &special, &formID); err != nil {
			armorRows.Close()
			return nil, fmt.Errorf("scan chest gear row: %w", err)
		}
		if strings.TrimSpace(formID) == "" {
			formID = "gear.form.chest." + subtype
		}
		out = append(out, model.GearFamily{
			Slot:          model.SlotChest,
			FormID:        formID,
			Name:          name,
			Subtype:       subtype,
			BasePrimary:   basePrimary,
			StatType:      statType,
			SpeedModifier: speed,
			BaseCrit:      crit,
			AgilityBonus:  agi,
			SetName:       setName,
			SpecialEffect: special,
		})
	}
	armorRows.Close()

	eqRows, err := s.pool.Query(ctx, `
		SELECT slot, form_id, name, base_primary, stat_type
		FROM equipment_items
	`)
	if err != nil {
		return nil, fmt.Errorf("load equipment_items from db: %w", err)
	}
	for eqRows.Next() {
		var slot, formID, name, statType string
		var basePrimary int
		if err := eqRows.Scan(&slot, &formID, &name, &basePrimary, &statType); err != nil {
			eqRows.Close()
			return nil, fmt.Errorf("scan equipment_item row: %w", err)
		}
		out = append(out, model.GearFamily{
			Slot:          normalizeEquipmentSlot(slot),
			FormID:        formID,
			Name:          name,
			BasePrimary:   basePrimary,
			StatType:      statType,
			SpeedModifier: 1.0,
		})
	}
	eqRows.Close()

	return out, nil
}
