"""
Skill tree and character class system.

This module defines the progression system including skill nodes, skill trees,
and player classes. Characters unlock skills by spending skill points earned
through leveling.
"""

from dataclasses import dataclass, field, asdict
from typing import Dict, Any, List, Optional

from app.models.stats import Stats


@dataclass
class SkillNode:
    """
    Represents a single skill in a skill tree.

    Skills can provide passive bonuses, unlock active abilities, or grant
    access to new features (like equipment types).

    Attributes:
        skill_id: Unique identifier
        name: Display name
        description: What this skill does
        tier: Skill tier (1-5, where 1 is basic and 5 is master)
        prerequisites: List of skill_ids that must be unlocked first
        effects: Dictionary of effects this skill provides
                Examples:
                - Passive bonuses: {"strength": 5, "defense": 10}
                - Ability unlocks: {"unlocks_ability": "shield_bash"}
                - Feature access: {"unlocks_equipment": "heavy_armor"}
        unlocked: Current unlock status (used during gameplay)
    """

    skill_id: str
    name: str
    description: str
    tier: int  # 1-5
    prerequisites: List[str] = field(default_factory=list)
    effects: Dict[str, Any] = field(default_factory=dict)
    unlocked: bool = False

    def has_prerequisites_met(self, unlocked_skills: List[str]) -> bool:
        """
        Check if all prerequisites for this skill are met.

        Args:
            unlocked_skills: List of skill_ids the character has unlocked

        Returns:
            True if all prerequisites are met, False otherwise
        """
        return all(prereq in unlocked_skills for prereq in self.prerequisites)

    def get_stat_bonuses(self) -> Dict[str, int]:
        """
        Extract stat bonuses from this skill's effects.

        Returns:
            Dictionary of stat bonuses (e.g., {"strength": 5, "defense": 3})
        """
        bonuses = {}
        for key, value in self.effects.items():
            # Look for stat names in effects
            if key in ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]:
                bonuses[key] = value
            elif key == "defense" or key == "resistance" or key == "hit_points" or key == "mana_points":
                bonuses[key] = value
        return bonuses

    def get_unlocked_abilities(self) -> List[str]:
        """
        Extract ability IDs unlocked by this skill.

        Returns:
            List of ability_ids this skill unlocks
        """
        abilities = []
        if "unlocks_ability" in self.effects:
            ability = self.effects["unlocks_ability"]
            if isinstance(ability, list):
                abilities.extend(ability)
            else:
                abilities.append(ability)
        return abilities

    def to_dict(self) -> Dict[str, Any]:
        """Serialize skill node to dictionary."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'SkillNode':
        """Deserialize skill node from dictionary."""
        return cls(
            skill_id=data["skill_id"],
            name=data["name"],
            description=data["description"],
            tier=data["tier"],
            prerequisites=data.get("prerequisites", []),
            effects=data.get("effects", {}),
            unlocked=data.get("unlocked", False),
        )


@dataclass
class SkillTree:
    """
    Represents a complete skill tree for a character class.

    Each class has 2+ skill trees representing different specializations.

    Attributes:
        tree_id: Unique identifier
        name: Display name (e.g., "Shield Bearer", "Pyromancy")
        description: Theme and purpose of this tree
        nodes: All skill nodes in this tree (organized by tier)
    """

    tree_id: str
    name: str
    description: str
    nodes: List[SkillNode] = field(default_factory=list)

    def can_unlock(self, skill_id: str, unlocked_skills: List[str]) -> bool:
        """
        Check if a specific skill can be unlocked.

        Validates:
        1. Skill exists in this tree
        2. Prerequisites are met
        3. Tier progression rules (must unlock tier N before tier N+1)

        Args:
            skill_id: The skill to check
            unlocked_skills: Currently unlocked skill_ids

        Returns:
            True if skill can be unlocked, False otherwise
        """
        # Find the skill node
        skill_node = None
        for node in self.nodes:
            if node.skill_id == skill_id:
                skill_node = node
                break

        if not skill_node:
            return False  # Skill not in this tree

        # Check if already unlocked
        if skill_id in unlocked_skills:
            return False

        # Check prerequisites
        if not skill_node.has_prerequisites_met(unlocked_skills):
            return False

        # Check tier progression
        # Must have at least one skill from previous tier unlocked
        # (except for tier 1 which is always available)
        if skill_node.tier > 1:
            has_previous_tier = False
            for node in self.nodes:
                if node.tier == skill_node.tier - 1 and node.skill_id in unlocked_skills:
                    has_previous_tier = True
                    break
            if not has_previous_tier:
                return False

        return True

    def get_nodes_by_tier(self, tier: int) -> List[SkillNode]:
        """
        Get all skill nodes for a specific tier.

        Args:
            tier: Tier number (1-5)

        Returns:
            List of SkillNodes at that tier
        """
        return [node for node in self.nodes if node.tier == tier]

    def to_dict(self) -> Dict[str, Any]:
        """Serialize skill tree to dictionary."""
        data = asdict(self)
        data["nodes"] = [node.to_dict() for node in self.nodes]
        return data

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'SkillTree':
        """Deserialize skill tree from dictionary."""
        nodes = [SkillNode.from_dict(n) for n in data.get("nodes", [])]
        return cls(
            tree_id=data["tree_id"],
            name=data["name"],
            description=data["description"],
            nodes=nodes,
        )


@dataclass
class PlayerClass:
    """
    Represents a character class (Vanguard, Assassin, Arcanist, etc.).

    Each class has unique base stats, multiple skill trees, and starting equipment.

    Attributes:
        class_id: Unique identifier
        name: Display name
        description: Class theme and playstyle
        base_stats: Starting stats for this class
        skill_trees: List of skill trees (2+ per class)
        starting_equipment: List of item_ids for initial equipment
        starting_abilities: List of ability_ids available from level 1
    """

    class_id: str
    name: str
    description: str
    base_stats: Stats
    skill_trees: List[SkillTree] = field(default_factory=list)
    starting_equipment: List[str] = field(default_factory=list)
    starting_abilities: List[str] = field(default_factory=list)

    def get_skill_tree(self, tree_id: str) -> Optional[SkillTree]:
        """
        Get a specific skill tree by ID.

        Args:
            tree_id: Skill tree identifier

        Returns:
            SkillTree instance or None if not found
        """
        for tree in self.skill_trees:
            if tree.tree_id == tree_id:
                return tree
        return None

    def get_all_skills(self) -> List[SkillNode]:
        """
        Get all skill nodes from all trees.

        Returns:
            Flat list of all SkillNodes across all trees
        """
        all_skills = []
        for tree in self.skill_trees:
            all_skills.extend(tree.nodes)
        return all_skills

    def to_dict(self) -> Dict[str, Any]:
        """Serialize player class to dictionary."""
        return {
            "class_id": self.class_id,
            "name": self.name,
            "description": self.description,
            "base_stats": self.base_stats.to_dict(),
            "skill_trees": [tree.to_dict() for tree in self.skill_trees],
            "starting_equipment": self.starting_equipment,
            "starting_abilities": self.starting_abilities,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'PlayerClass':
        """Deserialize player class from dictionary."""
        base_stats = Stats.from_dict(data["base_stats"])
        skill_trees = [SkillTree.from_dict(t) for t in data.get("skill_trees", [])]

        return cls(
            class_id=data["class_id"],
            name=data["name"],
            description=data["description"],
            base_stats=base_stats,
            skill_trees=skill_trees,
            starting_equipment=data.get("starting_equipment", []),
            starting_abilities=data.get("starting_abilities", []),
        )

    def __repr__(self) -> str:
        """String representation of the player class."""
        return (
            f"PlayerClass({self.name}, "
            f"trees={len(self.skill_trees)}, "
            f"total_skills={len(self.get_all_skills())})"
        )
