"""
Core Damage & Defense Calculation Engine for FreeExile (PoE2 Spirit).
Implements:
1. Deterministic Evasion Entropy to eliminate RNG streaks.
2. Non-linear Path of Exile Armour formula DR = Armour / (Armour + 5 * Dmg).
3. Elemental Resistance mitigation with hard caps and penetration.
4. Ward / Energy Shield pool absorption preceding Life depletion.
5. Boss Stagger / Poise accumulation and damage vulnerability window.
"""

from __future__ import annotations
import math
from typing import Dict, List, Optional, Tuple

from combat.combat_types import (
    AilmentApplication,
    AilmentType,
    BossPhase,
    BossStaggerState,
    CombatHitResult,
    CombatantType,
    DamageChannel,
    DamagePacket,
    DefenseSheet,
)


class DamageCalculator:
    """Pure mathematical and server-authoritative combat resolution pipeline."""

    @staticmethod
    def roll_accuracy_vs_evasion(
        accuracy: float, evasion: float, entropy_pool: int
    ) -> Tuple[bool, int]:
        """
        Calculates hit chance using PoE deterministic entropy system.
        Formula: Hit Chance = Accuracy / (Accuracy + (Evasion / 4)^0.8)
        Clamped between 5% and 95% unless evasion is 0 (100% hit).
        """
        if evasion <= 0.0:
            return True, entropy_pool

        acc = max(1.0, accuracy)
        denominator = acc + math.pow(max(0.0, evasion) / 4.0, 0.8)
        hit_chance = acc / denominator
        clamped_chance = min(0.95, max(0.05, hit_chance))

        new_entropy = entropy_pool + int(round(clamped_chance * 100.0))
        if new_entropy >= 100:
            return True, new_entropy - 100
        return False, new_entropy

    @staticmethod
    def calculate_armour_mitigation(armour: float, incoming_phys: float) -> float:
        """
        PoE Non-Linear Armour Damage Reduction (DR):
        DR = Armour / (Armour + 5.0 * IncomingPhysicalDamage)
        Capped at 90% (0.90).
        """
        if incoming_phys <= 0.0 or armour <= 0.0:
            return 0.0
        dr = armour / (armour + 5.0 * incoming_phys)
        return min(0.90, max(0.0, dr))

    @staticmethod
    def calculate_resistance_damage(
        raw_damage: float,
        resistance: float,
        max_cap: float = 0.75,
        penetration: float = 0.0,
    ) -> float:
        """
        Elemental & Chaos resistance damage resolution:
        Effective Res = min(Resistance, MaxCap) - Penetration
        Floor at -200% (-2.00).
        """
        if raw_damage <= 0.0:
            return 0.0
        effective_res = min(resistance, max_cap) - penetration
        effective_res = max(-2.00, effective_res)
        damage_multiplier = 1.0 - effective_res
        return max(0.0, raw_damage * damage_multiplier)

    def execute_hit(
        self,
        packet: DamagePacket,
        defense: DefenseSheet,
        entropy_pool: Optional[int] = None,
        boss_stagger: Optional[BossStaggerState] = None,
        force_crit: Optional[bool] = None,
    ) -> Tuple[CombatHitResult, int]:
        """Executes a single damage transaction through all defensive layers."""
        current_entropy = entropy_pool if entropy_pool is not None else defense.entropy_pool

        # 1. Huyễn Ảnh Bộ i-Frame Check (Invulnerability Window)
        if defense.is_in_iframe:
            return (
                CombatHitResult(
                    is_hit=False,
                    is_evaded=True,
                    is_blocked=False,
                    is_critical=False,
                    raw_damage_total=packet.damage_vector.total_raw(),
                    mitigated_damage_by_channel={},
                    absorbed_by_ward=0.0,
                    damage_to_life=0.0,
                    final_total_damage=0.0,
                    defender_life_remaining=defense.current_life,
                    defender_ward_remaining=defense.current_ward,
                ),
                current_entropy,
            )

        # 2. Accuracy vs Evasion Entropy Check
        is_hit, next_entropy = self.roll_accuracy_vs_evasion(
            packet.accuracy, defense.evasion_rating, current_entropy
        )
        defense.entropy_pool = next_entropy
        if not is_hit:
            return (
                CombatHitResult(
                    is_hit=False,
                    is_evaded=True,
                    is_blocked=False,
                    is_critical=False,
                    raw_damage_total=packet.damage_vector.total_raw(),
                    mitigated_damage_by_channel={},
                    absorbed_by_ward=0.0,
                    damage_to_life=0.0,
                    final_total_damage=0.0,
                    defender_life_remaining=defense.current_life,
                    defender_ward_remaining=defense.current_ward,
                ),
                next_entropy,
            )

        # 3. Block / Parry Check
        if defense.block_chance > 0.0 and defense.block_chance >= 1.0:
            return (
                CombatHitResult(
                    is_hit=False,
                    is_evaded=False,
                    is_blocked=True,
                    is_critical=False,
                    raw_damage_total=packet.damage_vector.total_raw(),
                    mitigated_damage_by_channel={},
                    absorbed_by_ward=0.0,
                    damage_to_life=0.0,
                    final_total_damage=0.0,
                    defender_life_remaining=defense.current_life,
                    defender_ward_remaining=defense.current_ward,
                ),
                next_entropy,
            )

        # 4. Global Damage Scaling & Multipliers
        global_multiplier = 1.0 + max(-0.90, packet.increased_damage_pct / 100.0)
        for more_mult in packet.more_damage_multipliers:
            global_multiplier *= more_mult

        is_crit = force_crit if force_crit is not None else (packet.crit_chance >= 1.0)
        if is_crit:
            global_multiplier *= packet.crit_multiplier

        if packet.is_aoe and defense.aoe_damage_reduction_pct > 0.0:
            global_multiplier *= (1.0 - min(0.90, defense.aoe_damage_reduction_pct))

        # 5. Channel Mitigation Evaluation
        mitigated_channels: Dict[DamageChannel, float] = {}

        # 5a. Physical Armour Mitigation
        raw_phys = packet.damage_vector.physical * global_multiplier
        if raw_phys > 0.0:
            phys_dr = self.calculate_armour_mitigation(defense.armour, raw_phys)
            mitigated_channels[DamageChannel.PHYSICAL] = raw_phys * (1.0 - phys_dr)

        # 5b. Elemental & Chaos Resistance Mitigation
        for ch in [DamageChannel.FIRE, DamageChannel.COLD, DamageChannel.LIGHTNING, DamageChannel.CHAOS]:
            raw_val = packet.damage_vector.get_channel(ch) * global_multiplier
            if raw_val > 0.0:
                res = defense.resistances.get(ch, 0.0)
                cap = defense.max_resistance_caps.get(ch, 0.75)
                pen = packet.elemental_penetration.get(ch, 0.0)
                mitigated_channels[ch] = self.calculate_resistance_damage(raw_val, res, cap, pen)

        # 6. Global Damage Taken Modifiers (Shock / Stagger / Fortify)
        total_mitigated = sum(mitigated_channels.values())
        taken_mult = defense.damage_taken_multiplier
        if boss_stagger and boss_stagger.is_staggered:
            taken_mult *= boss_stagger.stagger_damage_amplification

        final_damage = total_mitigated * taken_mult

        # 7. Absorption Order: Ward (Energy Shield) -> Life
        absorbed_ward = 0.0
        damage_to_life = final_damage

        if defense.current_ward > 0.0:
            if defense.current_ward >= final_damage:
                absorbed_ward = final_damage
                defense.current_ward -= final_damage
                damage_to_life = 0.0
            else:
                absorbed_ward = defense.current_ward
                damage_to_life = final_damage - defense.current_ward
                defense.current_ward = 0.0

        defense.current_life = max(0.0, defense.current_life - damage_to_life)

        # 8. Boss Stagger / Poise Evaluation (PoE2 Spirit)
        stagger_triggered = False
        stagger_applied = 0.0
        if boss_stagger and not boss_stagger.is_staggered:
            stagger_applied = packet.stagger_impact
            if stagger_applied <= 0.0 and DamageChannel.PHYSICAL in mitigated_channels:
                stagger_applied = mitigated_channels[DamageChannel.PHYSICAL] * 0.10

            boss_stagger.current_stagger += stagger_applied
            if boss_stagger.current_stagger >= boss_stagger.max_stagger_poise:
                boss_stagger.is_staggered = True
                boss_stagger.stagger_duration_remaining_sec = 4.0
                stagger_triggered = True

        return (
            CombatHitResult(
                is_hit=True,
                is_evaded=False,
                is_blocked=False,
                is_critical=is_crit,
                raw_damage_total=packet.damage_vector.total_raw() * global_multiplier,
                mitigated_damage_by_channel=mitigated_channels,
                absorbed_by_ward=round(absorbed_ward, 2),
                damage_to_life=round(damage_to_life, 2),
                final_total_damage=round(final_damage, 2),
                defender_life_remaining=round(defense.current_life, 2),
                defender_ward_remaining=round(defense.current_ward, 2),
                stagger_inflicted=round(stagger_applied, 2),
                boss_staggered_triggered=stagger_triggered,
            ),
            next_entropy,
        )
