import math
from typing import List, Optional, Tuple
try:
    from server.world.monster_types import (
        MonsterAIState,
        MonsterRuntimeState,
        MonsterAIAction,
        PackAuraType,
    )
except ImportError:
    from world.monster_types import (
        MonsterAIState,
        MonsterRuntimeState,
        MonsterAIAction,
        PackAuraType,
    )



class MonsterAIEngine:
    """Server-Authoritative AI Controller for feral monsters and packs."""

    def __init__(self) -> None:
        self._attack_reach_buffer_meters = 1.2
        self.pack_aura_radius_meters = 14.0

    def _calc_distance(self, x1: float, y1: float, x2: float, y2: float) -> float:
        return math.hypot(x2 - x1, y2 - y1)

    def apply_leader_aura(self, minion_state: MonsterRuntimeState, aura_type: PackAuraType) -> None:
        """Applies active leader aura buffs to minion runtime state."""
        if aura_type == PackAuraType.HASTE:
            minion_state.active_buffs["speed_mult"] = 1.30
            minion_state.active_buffs["cooldown_reduction"] = 0.30
        elif aura_type == PackAuraType.DAMAGE_RESISTANCE:
            minion_state.active_buffs["armor_mult"] = 1.50
            minion_state.active_buffs["res_mult"] = 1.25
        elif aura_type == PackAuraType.ELEMENTAL_EMPOWERMENT:
            minion_state.active_buffs["elemental_dmg_mult"] = 1.40
        elif aura_type == PackAuraType.VAMPIRIC:
            minion_state.active_buffs["life_leech_pct"] = 0.25
        elif aura_type == PackAuraType.MORTAL_MIGHT:
            minion_state.active_buffs["dmg_mult"] = 1.35
            minion_state.active_buffs["crit_chance_bonus"] = 0.20

    def clear_leader_aura(self, minion_state: MonsterRuntimeState) -> None:
        """Clears all aura-derived buffs when leader dies or minion is separated."""
        minion_state.active_buffs.clear()

    def _handle_leash_return(self, state: MonsterRuntimeState, dt: float) -> MonsterAIAction:
        dist_to_spawn = self._calc_distance(state.current_x, state.current_y, state.spawn_x, state.spawn_y)
        if dist_to_spawn <= 1.0:
            state.ai_state = MonsterAIState.IDLE
            state.is_invulnerable = False
            state.current_x = state.spawn_x
            state.current_y = state.spawn_y
            return MonsterAIAction(action_type="STAND", target_x=state.spawn_x, target_y=state.spawn_y)

        # Move rapidly back towards spawn with invulnerability and fast regen
        state.is_invulnerable = True
        state.current_hp = min(state.monster.max_hp, state.current_hp + state.monster.max_hp * 0.30 * dt)
        speed = state.monster.aggression.movement_speed * 1.5
        dx = (state.spawn_x - state.current_x) / dist_to_spawn
        dy = (state.spawn_y - state.current_y) / dist_to_spawn
        state.current_x += dx * speed * dt
        state.current_y += dy * speed * dt
        return MonsterAIAction(action_type="RETURN_TO_SPAWN", target_x=state.spawn_x, target_y=state.spawn_y)

    def update(
        self,
        state: MonsterRuntimeState,
        player_pos: Tuple[float, float],
        dt: float,
        current_time_ms: int = 0
    ) -> MonsterAIAction:
        """Executes a single tick (dt) AI evaluation."""
        if state.ai_state == MonsterAIState.DEAD:
            return MonsterAIAction(action_type="STAND", target_x=state.current_x, target_y=state.current_y)

        dist_from_spawn = self._calc_distance(state.current_x, state.current_y, state.spawn_x, state.spawn_y)
        dist_to_player = self._calc_distance(state.current_x, state.current_y, player_pos[0], player_pos[1])

        # 1. Anti-kiting Leash Check
        if dist_from_spawn > state.monster.aggression.leash_radius_meters or state.ai_state == MonsterAIState.LEASH_RETURN:
            state.ai_state = MonsterAIState.LEASH_RETURN
            return self._handle_leash_return(state, dt)

        # 2. Attack Range Check
        melee_range = state.monster.visual.collision_radius + self._attack_reach_buffer_meters
        if dist_to_player <= melee_range:
            state.ai_state = MonsterAIState.ATTACK
            dmg_amount = None
            cd_reduction = state.active_buffs.get("cooldown_reduction", 0.0)
            eff_cd = max(100, int(state.monster.aggression.attack_cooldown_ms * (1.0 - cd_reduction)))
            if current_time_ms - state.last_attack_timestamp_ms >= eff_cd:
                dmg_mult = state.active_buffs.get("dmg_mult", 1.0)
                dmg_amount = round(state.monster.base_damage * dmg_mult, 1)
                state.last_attack_timestamp_ms = current_time_ms
            return MonsterAIAction(
                action_type="ATTACK",
                target_x=player_pos[0],
                target_y=player_pos[1],
                damage_event=dmg_amount,
            )

        # 3. Aggro Radius Check or Active Pursuit
        should_pursue = (dist_to_player <= state.monster.aggression.aggro_radius_meters) or (state.ai_state == MonsterAIState.PURSUIT)
        if should_pursue:
            state.ai_state = MonsterAIState.PURSUIT
            speed_mult = state.active_buffs.get("speed_mult", 1.0)
            speed = state.monster.aggression.movement_speed * speed_mult
            dx = (player_pos[0] - state.current_x) / max(0.001, dist_to_player)
            dy = (player_pos[1] - state.current_y) / max(0.001, dist_to_player)
            state.current_x += dx * speed * dt
            state.current_y += dy * speed * dt
            return MonsterAIAction(
                action_type="MOVE_TOWARDS",
                target_x=player_pos[0],
                target_y=player_pos[1],
            )

        # 4. Out of Range -> Idle or Patrol
        state.ai_state = MonsterAIState.IDLE
        return MonsterAIAction(action_type="STAND", target_x=state.current_x, target_y=state.current_y)

    def update_pack(
        self,
        pack_states: List[MonsterRuntimeState],
        player_pos: Tuple[float, float],
        dt: float,
        current_time_ms: int = 0
    ) -> List[MonsterAIAction]:
        """
        Coordinates pack pursuit alerting and proximity leader aura buffs across pack members.
        """
        # 1. Identify pack leader and check alive status
        leader: Optional[MonsterRuntimeState] = None
        for s in pack_states:
            if s.is_pack_leader:
                leader = s
                break

        leader_alive = (
            leader is not None
            and leader.current_hp > 0
            and leader.ai_state != MonsterAIState.DEAD
        )

        # 2. Propagate or decay leader aura buffs based on proximity (< 14.0m) and alive state
        for s in pack_states:
            if s.is_pack_leader:
                if leader_alive and s.leader_aura:
                    self.apply_leader_aura(s, s.leader_aura)
                else:
                    self.clear_leader_aura(s)
            elif leader_alive and leader is not None and leader.leader_aura:
                dist_leader = self._calc_distance(s.current_x, s.current_y, leader.current_x, leader.current_y)
                if dist_leader <= self.pack_aura_radius_meters:
                    self.apply_leader_aura(s, leader.leader_aura)
                else:
                    self.clear_leader_aura(s)
            else:
                self.clear_leader_aura(s)

        # 3. Check for coordinated pack aggro broadcast
        pack_alerted = False
        for s in pack_states:
            if s.ai_state in (MonsterAIState.DEAD, MonsterAIState.LEASH_RETURN):
                continue
            dist_to_p = self._calc_distance(s.current_x, s.current_y, player_pos[0], player_pos[1])
            if dist_to_p <= s.monster.aggression.aggro_radius_meters or s.ai_state in (MonsterAIState.PURSUIT, MonsterAIState.ATTACK):
                pack_alerted = True
                break

        if pack_alerted:
            for s in pack_states:
                if s.ai_state in (MonsterAIState.IDLE, MonsterAIState.PATROL, MonsterAIState.ALERT):
                    s.ai_state = MonsterAIState.PURSUIT

        # 4. Update each monster individually
        return [self.update(s, player_pos, dt, current_time_ms) for s in pack_states]
