package actions

import (
	"dk/internal/models/fights"
	"dk/internal/models/monsters"
	"dk/internal/models/spells"
	"dk/internal/models/towns"
	"dk/internal/models/users"
	"math"
	"math/rand"
	"strconv"
)

func HandleAttack(fight *fights.Fight, user *users.User) {
	// Load monster data to get armor
	monster, err := monsters.Find(fight.MonsterID)
	if err != nil {
		fight.AddAction("Monster not found!")
		return
	}

	// Player attack damage calculation with sqrt scaling
	attackPower := float64(user.Attack)
	minAttack := attackPower * 0.75
	maxAttack := attackPower
	rawAttack := math.Ceil(rand.Float64()*(maxAttack-minAttack) + minAttack)

	// Progressive scaling using square root for smooth progression
	tohit := rawAttack / (1.2 + math.Sqrt(attackPower)*0.05)

	// Critical hit chance based on strength
	criticalRoll := rand.Intn(150) + 1
	if float64(criticalRoll) <= math.Sqrt(float64(user.Strength)) {
		tohit *= 2 // Critical hit
	}

	// Monster defense calculation with more aggressive scaling
	armor := float64(monster.Armor)
	minBlock := armor * 0.75
	maxBlock := armor
	rawBlock := math.Ceil(rand.Float64()*(maxBlock-minBlock) + minBlock)

	// Armor uses higher divisor to balance against player attack
	toblock := rawBlock / (1.8 + math.Sqrt(armor)*0.08)

	// Calculate final damage
	damage := tohit - toblock
	if damage < 1 {
		damage = 1 // Minimum damage
	}

	// Apply uber damage bonus
	if fight.UberDamage > 0 {
		bonus := math.Ceil(damage * float64(fight.UberDamage) / 100)
		damage += bonus
	}

	finalDamage := int(damage)

	// Apply damage and add action
	fight.DamageMonster(finalDamage)
	fight.AddActionAttackHit(finalDamage)

	// Check if monster is defeated
	if fight.MonsterHP <= 0 {
		fight.AddActionMonsterDeath(monster.Name)
		rewardGold, rewardExp := calculateRewards(monster, user)
		fight.WinFight(rewardGold, rewardExp)
		HandleFightWin(fight, user)
	}
}

func HandleSpell(fight *fights.Fight, user *users.User, spellID int) {
	spell, err := spells.Find(spellID)
	if err != nil {
		fight.AddAction("Spell not found!")
		return
	}

	// Check if user has enough MP
	if user.MP < spell.MP {
		fight.AddAction("Not enough MP to cast " + spell.Name + "!")
		return
	}

	// Check if user knows this spell
	if !user.HasSpell(spellID) {
		fight.AddAction("You don't know that spell!")
		return
	}

	// Deduct MP
	user.MP -= spell.MP

	switch spell.Type {
	case spells.TypeHealing:
		// Heal user
		healAmount := spell.Attribute
		user.HP += healAmount
		if user.HP > user.MaxHP {
			user.HP = user.MaxHP
		}
		fight.AddAction("You cast " + spell.Name + " and healed " + strconv.Itoa(healAmount) + " HP!")

	case spells.TypeHurt:
		// Damage monster
		damage := spell.Attribute
		fight.DamageMonster(damage)
		fight.AddAction("You cast " + spell.Name + " and dealt " + strconv.Itoa(damage) + " damage!")

		// Check if monster is defeated
		if fight.MonsterHP <= 0 {
			fight.WinFight(10, 5) // Basic rewards
		}

	default:
		fight.AddAction("You cast " + spell.Name + " but nothing happened!")
	}
}

func HandleRun(fight *fights.Fight, user *users.User) {
	// 20% chance to successfully run away
	if rand.Float32() < 0.2 {
		fight.RunAway()
		user.FightID = 0
		fight.AddAction("You successfully ran away!")
	} else {
		fight.AddAction("You failed to run away!")
	}
}

func HandleMonsterAttack(fight *fights.Fight, user *users.User) {
	// Load monster data
	monster, err := monsters.Find(fight.MonsterID)
	if err != nil {
		return
	}

	// Monster attack damage calculation
	attackPower := float64(monster.MaxDmg)
	minAttack := attackPower * 0.75
	maxAttack := attackPower
	tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3

	// User defense calculation
	defense := float64(user.Defense)
	minBlock := defense * 0.75
	maxBlock := defense
	toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3

	// Calculate final damage
	damage := tohit - toblock
	if damage < 1 {
		damage = 1 // Minimum damage
	}

	// Apply uber defense bonus (reduces damage taken)
	if fight.UberDefense > 0 {
		reduction := math.Ceil(damage * float64(fight.UberDefense) / 100)
		damage -= reduction
		if damage < 1 {
			damage = 1 // Still minimum 1 damage
		}
	}

	finalDamage := int(damage)

	// Apply damage to user
	user.HP -= finalDamage
	if user.HP < 0 {
		user.HP = 0
	}

	// Add monster attack action using memory-optimized format
	fight.AddActionMonsterAttack(monster.Name, finalDamage)

	// Check if user is defeated
	if user.HP <= 0 {
		fight.LoseFight()
		HandleFightLoss(fight, user)
	}
}

func HandleFightWin(fight *fights.Fight, user *users.User) {
	// Add rewards to user
	user.GrantExp(fight.RewardExp)
	user.Gold += fight.RewardGold

	// Reset fight state
	user.FightID = 0
	user.Currently = "Exploring"

	fight.Save()
	user.Save()
}

func HandleFightLoss(fight *fights.Fight, user *users.User) {
	// Find closest town to user's position
	closestTown := findClosestTown(user.X, user.Y)
	if closestTown != nil {
		user.X = closestTown.X
		user.Y = closestTown.Y
	}

	// Apply death penalties
	user.HP = user.MaxHP / 4        // 25% of max health
	user.Gold = (user.Gold * 3) / 4 // 75% of gold

	// Reset fight state
	user.FightID = 0
	user.Currently = "In Town"

	fight.Save()
	user.Save()
}

func findClosestTown(x, y int) *towns.Town {
	allTowns, err := towns.All()
	if err != nil || len(allTowns) == 0 {
		return nil
	}

	var closest *towns.Town
	var minDistance float64

	for _, town := range allTowns {
		distance := town.DistanceFromSquared(x, y)
		if closest == nil || distance < minDistance {
			closest = town
			minDistance = distance
		}
	}

	return closest
}

func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) {
	// Base rewards (83-100% of max)
	minExp := (monster.MaxExp * 5) / 6
	maxExp := monster.MaxExp
	exp := rand.Intn(maxExp-minExp+1) + minExp

	minGold := (monster.MaxGold * 5) / 6
	maxGold := monster.MaxGold
	gold := rand.Intn(maxGold-minGold+1) + minGold

	// Apply bonus multipliers
	expBonus := (user.ExpBonus * exp) / 100
	exp += expBonus

	goldBonus := (user.GoldBonus * gold) / 100
	gold += goldBonus

	return gold, exp
}
