"""
FreeExile Party Scaling Calculator.
Pure mathematical engine implementing Path of Exile 2 party scaling:
- Nearby radius spatial membership (15.0m, zone, instance)
- EXP sharing pool (+30%/player, (Lvl+10)^2.71 weighting, level gap decay)
- Item & Currency Quantity (+50%/player) and Rarity (+30%/player) multipliers
- Dynamic monster HP, Armour, Resistance (+2%/player cap 75%), and Poise scaling.

Follows Engineering Standards 2026: Soft cap <= 350 lines, strict typing, zero allocations in hot paths.
"""

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

from .party_types import PartyMemberState


class PartyScalingCalculator:
    """Authoritative mathematical oracle for PoE2 party scaling formulas."""

    NEARBY_RADIUS_DEFAULT: float = 15.0

    @staticmethod
    def calculate_nearby_members(
        members: List[PartyMemberState],
        ref_wx: float,
        ref_wy: float,
        ref_zone: str,
        ref_inst: str,
        radius: float = NEARBY_RADIUS_DEFAULT,
    ) -> List[PartyMemberState]:
        """
        Filters party members that are within the 15.0m nearby radius.
        Requires exact match for zone_id, instance_id, and euclidean distance <= radius.
        """
        nearby: List[PartyMemberState] = []
        for m in members:
            if not m.is_online:
                continue
            if m.zone_id != ref_zone or m.instance_id != ref_inst:
                continue
            m_x = getattr(m, "wx", getattr(m, "x", 0.0))
            m_y = getattr(m, "wy", getattr(m, "y", 0.0))
            dist = math.hypot(m_x - ref_wx, m_y - ref_wy)
            if dist <= radius:
                nearby.append(m)
        return nearby

    @staticmethod
    def calculate_level_gap_multiplier(player_level: int, monster_level: int) -> float:
        """
        Calculates experience yield multiplier based on character-monster level gap.
        Matches LevelProgressionService authoritative formula.
        """
        gap = player_level - monster_level
        if abs(gap) <= 5:
            return 1.0
        if gap > 5:
            delta = gap - 5
            return max(0.01, math.exp(-0.60 * delta))
        delta = (-gap) - 5
        return max(0.05, math.exp(-0.40 * delta))

    @staticmethod
    def calculate_party_exp(
        base_exp: int,
        members: List[PartyMemberState],
        monster_level: Optional[int] = None,
    ) -> Dict[str, int]:
        """
        Distributes monster experience among nearby party members:
        - Total Pool: BaseEXP * (1.0 + 0.30 * (N - 1))
        - Share Weight: W_i = (Level_i + 10)^2.71
        - Optional: Applies personal level gap penalty if monster_level provided.
        """
        n = len(members)
        if n == 0 or base_exp <= 0:
            return {}

        pool_mult = 1.0 + 0.30 * (n - 1)
        total_pool = int(math.floor(base_exp * pool_mult))

        weights: Dict[str, float] = {}
        for m in members:
            weights[m.player_id] = math.pow(float(m.level + 10), 2.71)

        total_weight = sum(weights.values())
        if total_weight <= 0.0:
            raw_shares = {m.player_id: total_pool // n for m in members}
        else:
            raw_shares = {
                m.player_id: int(math.floor(total_pool * (weights[m.player_id] / total_weight)))
                for m in members
            }

        if monster_level is not None:
            final_shares: Dict[str, int] = {}
            for m in members:
                raw = raw_shares[m.player_id]
                gap_mult = PartyScalingCalculator.calculate_level_gap_multiplier(m.level, monster_level)
                final_shares[m.player_id] = int(math.floor(raw * gap_mult))
            return final_shares

        return raw_shares

    @staticmethod
    def calculate_loot_multipliers(nearby_count: int) -> Tuple[float, float, float]:
        """
        Calculates (item_quantity, currency_quantity, rarity) multipliers:
        - Item Quantity: 1.0 + 0.50 * (N - 1)
        - Currency Quantity: 1.0 + 0.50 * (N - 1)
        - Rarity Multiplier: 1.0 + 0.30 * (N - 1)
        """
        if nearby_count <= 0:
            return 1.0, 1.0, 1.0
        extra = max(0, nearby_count - 1)
        item_qty = 1.0 + 0.50 * extra
        curr_qty = 1.0 + 0.50 * extra
        rarity = 1.0 + 0.30 * extra
        return item_qty, curr_qty, rarity

    @staticmethod
    def calculate_monster_scaling(
        arg1: Optional[Union[int, float, str]] = None,
        arg2: Optional[Union[str, int]] = None,
        party_count: Optional[int] = None,
        *,
        party_size: Optional[int] = None,
        base_hp: Optional[Union[int, float]] = None,
        rank: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Calculates scaled monster defenses:
        Supports both signatures:
          calculate_monster_scaling("COMMON", party_size=6)
          calculate_monster_scaling(base_hp=1000, rank="BOSS", party_count=6)
        """
        resolved_base_hp: Optional[float] = None
        resolved_rank: str = "COMMON"
        resolved_size: int = 1

        if isinstance(arg1, (int, float)):
            resolved_base_hp = float(arg1)
            if isinstance(arg2, str):
                resolved_rank = arg2
            if party_count is not None:
                resolved_size = party_count
            elif isinstance(arg2, int):
                resolved_size = arg2
        elif isinstance(arg1, str):
            resolved_rank = arg1
            if isinstance(arg2, int):
                resolved_size = arg2

        if party_count is not None:
            resolved_size = party_count
        if party_size is not None:
            resolved_size = party_size
        if base_hp is not None:
            resolved_base_hp = float(base_hp)
        if rank is not None:
            resolved_rank = rank

        extra = max(0, resolved_size - 1)
        rank_upper = resolved_rank.upper()

        if "BOSS" in rank_upper:
            hp_mult = 1.0 + 1.00 * extra
        elif "RARE" in rank_upper:
            hp_mult = 1.0 + 0.70 * extra
        else:  # COMMON, MAGIC
            hp_mult = 1.0 + 0.50 * extra

        armor_mult = 1.0 + 0.10 * extra
        res_bonus = min(75.0, 2.0 * extra)
        poise_ratio = 0.15

        res: Dict[str, Any] = {
            "hp_mult": hp_mult,
            "armor_mult": armor_mult,
            "res_bonus": res_bonus,
            "poise_ratio": poise_ratio,
        }

        if resolved_base_hp is not None:
            scaled_hp = int(round(resolved_base_hp * hp_mult))
            res["scaled_hp"] = scaled_hp
            res["poise_max"] = round(scaled_hp * poise_ratio, 1)

        return res
