"""
Ability system for combat actions and spells.

This module defines abilities (attacks, spells, skills) that can be used in combat.
Abilities are loaded from YAML configuration files for data-driven design.
"""

from dataclasses import dataclass, field, asdict
from typing import Dict, Any, List, Optional
import yaml
import os
from pathlib import Path

from app.models.enums import AbilityType, DamageType, EffectType, StatType
from app.models.effects import Effect
from app.models.stats import Stats


@dataclass
class Ability:
    """
    Represents an action that can be taken in combat.

    Abilities can deal damage, apply effects, heal, or perform other actions.
    They are loaded from YAML files for easy game design iteration.

    Attributes:
        ability_id: Unique identifier
        name: Display name
        description: What the ability does
        ability_type: Category (attack, spell, skill, etc.)
        base_power: Base damage or healing value
        damage_type: Type of damage dealt (physical, fire, etc.)
        scaling_stat: Which stat scales this ability's power (if any)
        scaling_factor: Multiplier for scaling stat (default 0.5)
        mana_cost: MP required to use this ability
        cooldown: Turns before ability can be used again
        effects_applied: List of effects applied to target on hit
        is_aoe: Whether this affects multiple targets
        target_count: Number of targets if AoE (0 = all)
    """

    ability_id: str
    name: str
    description: str
    ability_type: AbilityType
    base_power: int = 0
    damage_type: Optional[DamageType] = None
    scaling_stat: Optional[StatType] = None
    scaling_factor: float = 0.5
    mana_cost: int = 0
    cooldown: int = 0
    effects_applied: List[Effect] = field(default_factory=list)
    is_aoe: bool = False
    target_count: int = 1

    def calculate_power(self, caster_stats: Stats) -> int:
        """
        Calculate final power based on caster's stats.

        Formula: base_power + (scaling_stat × scaling_factor)
        Minimum power is always 1.

        Args:
            caster_stats: The caster's effective stats

        Returns:
            Final power value for damage or healing
        """
        power = self.base_power

        if self.scaling_stat:
            stat_value = getattr(caster_stats, self.scaling_stat.value)
            power += int(stat_value * self.scaling_factor)

        return max(1, power)

    def get_effects_to_apply(self) -> List[Effect]:
        """
        Get a copy of effects that should be applied to target(s).

        Creates new Effect instances to avoid sharing references.

        Returns:
            List of Effect instances to apply
        """
        return [
            Effect(
                effect_id=f"{self.ability_id}_{effect.name}_{id(effect)}",
                name=effect.name,
                effect_type=effect.effect_type,
                duration=effect.duration,
                power=effect.power,
                stat_affected=effect.stat_affected,
                stacks=effect.stacks,
                max_stacks=effect.max_stacks,
                source=self.ability_id,
            )
            for effect in self.effects_applied
        ]

    def to_dict(self) -> Dict[str, Any]:
        """
        Serialize ability to a dictionary.

        Returns:
            Dictionary containing all ability data
        """
        data = asdict(self)
        data["ability_type"] = self.ability_type.value
        if self.damage_type:
            data["damage_type"] = self.damage_type.value
        if self.scaling_stat:
            data["scaling_stat"] = self.scaling_stat.value
        data["effects_applied"] = [effect.to_dict() for effect in self.effects_applied]
        return data

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Ability':
        """
        Deserialize ability from a dictionary.

        Args:
            data: Dictionary containing ability data

        Returns:
            Ability instance
        """
        # Convert string values back to enums
        ability_type = AbilityType(data["ability_type"])
        damage_type = DamageType(data["damage_type"]) if data.get("damage_type") else None
        scaling_stat = StatType(data["scaling_stat"]) if data.get("scaling_stat") else None

        # Deserialize effects
        effects = []
        if "effects_applied" in data and data["effects_applied"]:
            effects = [Effect.from_dict(e) for e in data["effects_applied"]]

        return cls(
            ability_id=data["ability_id"],
            name=data["name"],
            description=data["description"],
            ability_type=ability_type,
            base_power=data.get("base_power", 0),
            damage_type=damage_type,
            scaling_stat=scaling_stat,
            scaling_factor=data.get("scaling_factor", 0.5),
            mana_cost=data.get("mana_cost", 0),
            cooldown=data.get("cooldown", 0),
            effects_applied=effects,
            is_aoe=data.get("is_aoe", False),
            target_count=data.get("target_count", 1),
        )

    def __repr__(self) -> str:
        """String representation of the ability."""
        return (
            f"Ability({self.name}, {self.ability_type.value}, "
            f"power={self.base_power}, cost={self.mana_cost}MP, "
            f"cooldown={self.cooldown}t)"
        )


class AbilityLoader:
    """
    Loads abilities from YAML configuration files.

    This allows game designers to define abilities without touching code.
    """

    def __init__(self, data_dir: Optional[str] = None):
        """
        Initialize the ability loader.

        Args:
            data_dir: Path to directory containing ability YAML files
                     Defaults to /app/data/abilities/
        """
        if data_dir is None:
            # Default to app/data/abilities relative to this file
            current_file = Path(__file__)
            app_dir = current_file.parent.parent  # Go up to /app
            data_dir = str(app_dir / "data" / "abilities")

        self.data_dir = Path(data_dir)
        self._ability_cache: Dict[str, Ability] = {}

    def load_ability(self, ability_id: str) -> Optional[Ability]:
        """
        Load a single ability by ID.

        Args:
            ability_id: Unique ability identifier

        Returns:
            Ability instance or None if not found
        """
        # Check cache first
        if ability_id in self._ability_cache:
            return self._ability_cache[ability_id]

        # Load from YAML file
        yaml_file = self.data_dir / f"{ability_id}.yaml"
        if not yaml_file.exists():
            return None

        with open(yaml_file, 'r') as f:
            data = yaml.safe_load(f)

        ability = Ability.from_dict(data)
        self._ability_cache[ability_id] = ability
        return ability

    def load_all_abilities(self) -> Dict[str, Ability]:
        """
        Load all abilities from the data directory.

        Returns:
            Dictionary mapping ability_id to Ability instance
        """
        if not self.data_dir.exists():
            return {}

        abilities = {}
        for yaml_file in self.data_dir.glob("*.yaml"):
            with open(yaml_file, 'r') as f:
                data = yaml.safe_load(f)

            ability = Ability.from_dict(data)
            abilities[ability.ability_id] = ability
            self._ability_cache[ability.ability_id] = ability

        return abilities

    def clear_cache(self) -> None:
        """Clear the ability cache, forcing reload on next access."""
        self._ability_cache.clear()
