package game

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"time"

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

// RollTownMerchantStockItems generates `count` gear rows for town-tier stock (one roll per slot order, unique slots first).
func RollTownMerchantStockItems(refLevel int, count int) []*model.GearItem {
	return RollTownMerchantStockItemsForSlots(refLevel, count, model.AllEquipmentSlots)
}

// RollTownMerchantStockItemsForSlots rolls gear only from the given slots (e.g. per vendor type).
func RollTownMerchantStockItemsForSlots(refLevel int, count int, slots []model.EquipmentSlot) []*model.GearItem {
	if count < 1 {
		count = 1
	}
	if len(slots) == 0 {
		slots = model.AllEquipmentSlots
	}
	if count > len(slots) {
		count = len(slots)
	}
	perm := rand.Perm(len(slots))
	out := make([]*model.GearItem, 0, count)
	for i := 0; i < count; i++ {
		slot := slots[perm[i]]
		family := model.PickGearFamily(slot)
		if family == nil {
			continue
		}
		rarity := model.RollRarity()
		ilvl := model.RollIlvl(refLevel, false)
		out = append(out, model.NewGearItem(family, ilvl, rarity))
	}
	for len(out) < count {
		slot := slots[rand.Intn(len(slots))]
		family := model.PickGearFamily(slot)
		if family == nil {
			continue
		}
		rarity := model.RollRarity()
		ilvl := model.RollIlvl(refLevel, false)
		out = append(out, model.NewGearItem(family, ilvl, rarity))
	}
	return out
}

func townMerchantRarityPriceMul(r model.Rarity) float64 {
	switch r {
	case model.RarityLegendary:
		return 3.4
	case model.RarityEpic:
		return 2.25
	case model.RarityRare:
		return 1.65
	case model.RarityUncommon:
		return 1.28
	default:
		return 1.0
	}
}

// RollTownMerchantOfferGold returns a per-item list buy price: town anchor + ilvl (× rarity), then uniform ±variance%.
// Inventory sell prices stay on model.AutoSellPrice (runtime autoSell*); they are not derived from this value.
func RollTownMerchantOfferGold(ilvl int, rarity model.Rarity, townLevel int) int64 {
	if ilvl < 1 {
		ilvl = 1
	}
	anchor := tuning.EffectiveTownMerchantGearCost(townLevel)
	perIlvl := tuning.EffectiveMerchantTownGearPricePerIlvl()
	variance := tuning.EffectiveMerchantTownGearPriceVariancePct()
	ilvlPart := float64(ilvl) * float64(perIlvl) * townMerchantRarityPriceMul(rarity)
	curve := float64(ilvl*ilvl) / 6.0
	mean := float64(anchor) + ilvlPart + curve
	if mean < 1 {
		mean = 1
	}
	v := float64(variance) / 100.0
	if v < 0 {
		v = 0
	}
	if v > 0.45 {
		v = 0.45
	}
	// Uniform in [1−v, 1+v] (e.g. v=0.15 → 85%..115% of mean).
	factor := (1.0 - v) + rand.Float64()*(2*v)
	cost := int64(mean*factor + 0.5)
	if cost < 1 {
		cost = 1
	}
	return cost
}

// ApplyPreparedTownMerchantPurchase persists a rolled item (id 0) and force-equips it.
func ApplyPreparedTownMerchantPurchase(ctx context.Context, gs *storage.GearStore, hero *model.Hero, item *model.GearItem, now time.Time) (*model.LootDrop, error) {
	if gs == nil || hero == nil || item == nil {
		return nil, errors.New("nil gear store, hero, or item")
	}
	toCreate := model.CloneGearItem(item)
	if toCreate == nil {
		return nil, errors.New("nil item clone")
	}

	ctxCreate, cancel := context.WithTimeout(ctx, 2*time.Second)
	err := gs.CreateItem(ctxCreate, toCreate)
	cancel()
	if err != nil {
		return nil, fmt.Errorf("create gear: %w", err)
	}

	ctxEq, cancelEq := context.WithTimeout(ctx, 2*time.Second)
	err = gs.EquipItem(ctxEq, hero.ID, toCreate.Slot, toCreate.ID)
	cancelEq()
	if err != nil {
		ctxDel, cancelDel := context.WithTimeout(ctx, 2*time.Second)
		_ = gs.DeleteGearItem(ctxDel, toCreate.ID)
		cancelDel()
		return nil, err
	}

	ctxLoad, cancelLoad := context.WithTimeout(ctx, 2*time.Second)
	gear, err := gs.GetHeroGear(ctxLoad, hero.ID)
	cancelLoad()
	if err != nil {
		return nil, fmt.Errorf("reload gear: %w", err)
	}
	ctxInv, cancelInv := context.WithTimeout(ctx, 2*time.Second)
	inv, err := gs.GetHeroInventory(ctxInv, hero.ID)
	cancelInv()
	if err != nil {
		return nil, fmt.Errorf("reload inventory: %w", err)
	}
	hero.Gear = gear
	hero.Inventory = inv
	hero.RefreshDerivedCombatStats(now)

	return &model.LootDrop{
		ItemType: string(toCreate.Slot),
		ItemID:   toCreate.ID,
		ItemName: toCreate.Name,
		Rarity:   toCreate.Rarity,
	}, nil
}

// ApplyTownMerchantGearPurchase rolls one gear piece using refLevel for ilvl (town tier),
// persists it, and force-equips into the hero slot (previous piece moves to backpack — same as admin EquipItem).
func ApplyTownMerchantGearPurchase(ctx context.Context, gs *storage.GearStore, hero *model.Hero, refLevel int, now time.Time) (*model.LootDrop, error) {
	items := RollTownMerchantStockItems(refLevel, 1)
	if len(items) == 0 {
		return nil, errors.New("failed to roll gear family")
	}
	return ApplyPreparedTownMerchantPurchase(ctx, gs, hero, items[0], now)
}

// TownMerchantRollIsUpgrade returns true if equipping the rolled item (hypothetically) raises CombatRatingAt.
func TownMerchantRollIsUpgrade(hero *model.Hero, item *model.GearItem, now time.Time) bool {
	if hero == nil || item == nil {
		return false
	}
	hero.EnsureGearMap()
	before := hero.CombatRatingAt(now)
	old := hero.Gear[item.Slot]
	hero.Gear[item.Slot] = item
	after := hero.CombatRatingAt(now)
	hero.Gear[item.Slot] = old
	return after > before
}
