package model

import (
	"math/rand"
	"time"

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

// Rarity represents item rarity tiers. Shared across weapons, armor, and loot.
type Rarity string

const (
	RarityCommon    Rarity = "common"
	RarityUncommon  Rarity = "uncommon"
	RarityRare      Rarity = "rare"
	RarityEpic      Rarity = "epic"
	RarityLegendary Rarity = "legendary"
)

// GoldRange defines minimum and maximum gold drops per rarity.
type GoldRange struct {
	Min int64
	Max int64
}

// LootDrop represents a single item or gold drop from defeating an enemy.
type LootDrop struct {
	ItemType   string `json:"itemType"`             // "gold", "potion", or EquipmentSlot ("main_hand", "chest", "head", etc.)
	ItemID     int64  `json:"itemId,omitempty"`     // ID of the weapon or armor, 0 for gold
	ItemName   string `json:"itemName,omitempty"`   // display name when equipped / dropped
	Rarity     Rarity `json:"rarity"`
	GoldAmount int64  `json:"goldAmount,omitempty"` // gold value of this drop
}

// LootHistory records a loot drop event for audit/analytics.
type LootHistory struct {
	ID         int64     `json:"id"`
	HeroID     int64     `json:"heroId"`
	EnemyType  string    `json:"enemyType"`
	ItemType   string    `json:"itemType"`
	ItemID     int64     `json:"itemId,omitempty"`
	Rarity     Rarity    `json:"rarity"`
	GoldAmount int64     `json:"goldAmount"`
	CreatedAt  time.Time `json:"createdAt"`
}

// RollRarity rolls a random rarity based on the drop chance table.
// It uses a cumulative probability approach, checking from rarest to most common.
func RollRarity() Rarity {
	return RarityFromRoll(rand.Float64())
}

// RarityFromRoll maps a uniform [0,1) value to a rarity tier (spec §8.1 drop bands).
func RarityFromRoll(roll float64) Rarity {
	cfg := tuning.Get()
	if roll < cfg.LootChanceLegendary {
		return RarityLegendary
	}
	if roll < cfg.LootChanceLegendary+cfg.LootChanceEpic {
		return RarityEpic
	}
	if roll < cfg.LootChanceLegendary+cfg.LootChanceEpic+cfg.LootChanceRare {
		return RarityRare
	}
	if roll < cfg.LootChanceLegendary+cfg.LootChanceEpic+cfg.LootChanceRare+cfg.LootChanceUncommon {
		return RarityUncommon
	}
	return RarityCommon
}

// RollGold returns a random gold amount for the given rarity.
func RollGold(rarity Rarity) int64 {
	return RollGoldWithRNG(rarity, nil)
}

// RollGoldWithRNG returns spec §8.2 gold for a rarity tier; if rng is nil, uses the global RNG.
func RollGoldWithRNG(rarity Rarity, rng *rand.Rand) int64 {
	cfg := tuning.Get()
	gr, ok := GoldRangeForRarity(cfg, rarity)
	if !ok {
		return 0
	}
	if gr.Max <= gr.Min {
		return gr.Min
	}
	var n int64
	if rng == nil {
		n = gr.Min + rand.Int63n(gr.Max-gr.Min+1)
	} else {
		n = gr.Min + rng.Int63n(gr.Max-gr.Min+1)
	}
	out := int64(float64(n) * cfg.GoldLootScale)
	if out < 1 {
		out = 1
	}
	return out
}

func GoldRangeForRarity(cfg tuning.Values, rarity Rarity) (GoldRange, bool) {
	switch rarity {
	case RarityCommon:
		return GoldRange{Min: cfg.GoldCommonMin, Max: cfg.GoldCommonMax}, true
	case RarityUncommon:
		return GoldRange{Min: cfg.GoldUncommonMin, Max: cfg.GoldUncommonMax}, true
	case RarityRare:
		return GoldRange{Min: cfg.GoldRareMin, Max: cfg.GoldRareMax}, true
	case RarityEpic:
		return GoldRange{Min: cfg.GoldEpicMin, Max: cfg.GoldEpicMax}, true
	case RarityLegendary:
		return GoldRange{Min: cfg.GoldLegendaryMin, Max: cfg.GoldLegendaryMax}, true
	default:
		return GoldRange{}, false
	}
}

// equipmentLootSlots maps loot ItemType strings to relative weights.
// All item types now use unified EquipmentSlot names.
var equipmentLootSlots = []struct {
	itemType string
	weight   float64
}{
	{string(SlotMainHand), 0.05},
	{string(SlotChest), 0.05},
	{string(SlotHead), 0.05},
	{string(SlotFeet), 0.05},
	{string(SlotNeck), 0.05},
	{string(SlotHands), 0.05},
	{string(SlotLegs), 0.05},
	{string(SlotCloak), 0.05},
	{string(SlotFinger), 0.05},
	{string(SlotWrist), 0.05},
}

func rollEquipmentLootItemType(float01 func() float64) string {
	r := float01()
	var acc float64
	for _, row := range equipmentLootSlots {
		acc += row.weight
		if r < acc {
			return row.itemType
		}
	}
	return equipmentLootSlots[len(equipmentLootSlots)-1].itemType
}

// GenerateLoot generates loot drops from defeating an enemy (preview / tests).
// Guaranteed gold uses a spec rarity band; optional equipment is independent and does not replace gold.
func GenerateLoot(enemyType EnemyType, luckMultiplier float64) []LootDrop {
	return GenerateLootWithRNG(enemyType, luckMultiplier, nil)
}

// GenerateLootWithRNG is GenerateLoot with an optional RNG for deterministic tests.
func GenerateLootWithRNG(enemyType EnemyType, luckMultiplier float64, rng *rand.Rand) []LootDrop {
	var drops []LootDrop

	float01 := func() float64 {
		if rng == nil {
			return rand.Float64()
		}
		return rng.Float64()
	}

	// Gold tier roll (spec §8.1–8.2); independent of whether an item drops later.
	goldRarity := RarityFromRoll(float01())
	goldAmount := RollGoldWithRNG(goldRarity, rng)
	if luckMultiplier > 1 {
		goldAmount = int64(float64(goldAmount) * luckMultiplier)
	}

	drops = append(drops, LootDrop{
		ItemType:   "gold",
		Rarity:     goldRarity,
		GoldAmount: goldAmount,
	})

	cfg := tuning.Get()
	// Configurable chance to drop a healing potion.
	potionRoll := float01()
	if potionRoll < cfg.PotionDropChance {
		drops = append(drops, LootDrop{
			ItemType: "potion",
			Rarity:   RarityCommon,
		})
	}

	equipRoll := float01()
	equipChance := cfg.EquipmentDropBase * luckMultiplier
	if equipChance > 1 {
		equipChance = 1
	}

	if equipRoll < equipChance {
		itemRarity := RarityFromRoll(float01())
		itemType := rollEquipmentLootItemType(float01)
		drops = append(drops, LootDrop{
			ItemType: itemType,
			Rarity:   itemRarity,
		})
	}

	return drops
}

func AutoSellPrice(rarity Rarity) int64 {
	cfg := tuning.Get()
	switch rarity {
	case RarityCommon:
		return cfg.AutoSellCommon
	case RarityUncommon:
		return cfg.AutoSellUncommon
	case RarityRare:
		return cfg.AutoSellRare
	case RarityEpic:
		return cfg.AutoSellEpic
	case RarityLegendary:
		return cfg.AutoSellLegendary
	default:
		return 0
	}
}
