"""
Character data model - the core entity for player characters.

This module defines the Character dataclass which represents a player's character
with all their stats, inventory, progression, and the critical get_effective_stats()
method that calculates final stats from all sources.
"""

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

from app.models.stats import Stats
from app.models.items import Item
from app.models.skills import PlayerClass, SkillNode
from app.models.effects import Effect
from app.models.enums import EffectType, StatType
from app.models.origins import Origin


@dataclass
class Character:
    """
    Represents a player's character.

    This is the central data model that ties together all character-related data:
    stats, class, inventory, progression, and quests.

    The critical method is get_effective_stats() which calculates the final stats
    by combining base stats + equipment bonuses + skill bonuses + active effects.

    Attributes:
        character_id: Unique identifier
        user_id: Owner's user ID (from Appwrite auth)
        name: Character name
        player_class: Character's class (determines base stats and skill trees)
        origin: Character's backstory origin (saved for AI DM narrative hooks)
        level: Current level
        experience: Current XP points
        base_stats: Base stats (from class + level-ups)
        unlocked_skills: List of skill_ids that have been unlocked
        inventory: All items the character owns
        equipped: Currently equipped items by slot
                 Slots: "weapon", "armor", "helmet", "boots", "accessory", etc.
        gold: Currency amount
        active_quests: List of quest IDs currently in progress
        discovered_locations: List of location IDs the character has visited
        current_location: Current location ID (tracks character position)
    """

    character_id: str
    user_id: str
    name: str
    player_class: PlayerClass
    origin: Origin
    level: int = 1
    experience: int = 0

    # Stats and progression
    base_stats: Stats = field(default_factory=Stats)
    unlocked_skills: List[str] = field(default_factory=list)

    # Inventory and equipment
    inventory: List[Item] = field(default_factory=list)
    equipped: Dict[str, Item] = field(default_factory=dict)
    gold: int = 0

    # Quests and exploration
    active_quests: List[str] = field(default_factory=list)
    discovered_locations: List[str] = field(default_factory=list)
    current_location: Optional[str] = None  # Set to origin starting location on creation

    # NPC interaction tracking (persists across sessions)
    # Each entry: {npc_id: {interaction_count, relationship_level, dialogue_history, ...}}
    # dialogue_history: List[{player_line: str, npc_response: str}]
    npc_interactions: Dict[str, Dict] = field(default_factory=dict)

    def get_effective_stats(self, active_effects: Optional[List[Effect]] = None) -> Stats:
        """
        Calculate final effective stats from all sources.

        This is the CRITICAL METHOD that combines:
        1. Base stats (from character)
        2. Equipment bonuses (from equipped items)
        3. Skill tree bonuses (from unlocked skills)
        4. Active effect modifiers (buffs/debuffs)

        Args:
            active_effects: Currently active effects on this character (from combat)

        Returns:
            Stats instance with all modifiers applied
        """
        # Start with a copy of base stats
        effective = self.base_stats.copy()

        # Apply equipment bonuses
        for item in self.equipped.values():
            for stat_name, bonus in item.stat_bonuses.items():
                if hasattr(effective, stat_name):
                    current_value = getattr(effective, stat_name)
                    setattr(effective, stat_name, current_value + bonus)

        # Apply skill tree bonuses
        skill_bonuses = self._get_skill_bonuses()
        for stat_name, bonus in skill_bonuses.items():
            if hasattr(effective, stat_name):
                current_value = getattr(effective, stat_name)
                setattr(effective, stat_name, current_value + bonus)

        # Apply active effect modifiers (buffs/debuffs)
        if active_effects:
            for effect in active_effects:
                if effect.effect_type in [EffectType.BUFF, EffectType.DEBUFF]:
                    if effect.stat_affected:
                        stat_name = effect.stat_affected.value
                        if hasattr(effective, stat_name):
                            current_value = getattr(effective, stat_name)
                            modifier = effect.power * effect.stacks

                            if effect.effect_type == EffectType.BUFF:
                                setattr(effective, stat_name, current_value + modifier)
                            else:  # DEBUFF
                                # Stats can't go below 1
                                setattr(effective, stat_name, max(1, current_value - modifier))

        return effective

    def _get_skill_bonuses(self) -> Dict[str, int]:
        """
        Calculate total stat bonuses from unlocked skills.

        Returns:
            Dictionary of stat bonuses from skill tree
        """
        bonuses: Dict[str, int] = {}

        # Get all skill nodes from all trees
        all_skills = self.player_class.get_all_skills()

        # Sum up bonuses from unlocked skills
        for skill in all_skills:
            if skill.skill_id in self.unlocked_skills:
                skill_bonuses = skill.get_stat_bonuses()
                for stat_name, bonus in skill_bonuses.items():
                    bonuses[stat_name] = bonuses.get(stat_name, 0) + bonus

        return bonuses

    def get_unlocked_abilities(self) -> List[str]:
        """
        Get all ability IDs unlocked by this character's skills.

        Returns:
            List of ability_ids from skill tree + class starting abilities
        """
        abilities = list(self.player_class.starting_abilities)

        # Get all skill nodes from all trees
        all_skills = self.player_class.get_all_skills()

        # Collect abilities from unlocked skills
        for skill in all_skills:
            if skill.skill_id in self.unlocked_skills:
                abilities.extend(skill.get_unlocked_abilities())

        return abilities

    @property
    def class_id(self) -> str:
        """Get class ID for template access."""
        return self.player_class.class_id

    @property
    def origin_id(self) -> str:
        """Get origin ID for template access."""
        return self.origin.id

    @property
    def origin_name(self) -> str:
        """Get origin display name for template access."""
        return self.origin.name

    @property
    def available_skill_points(self) -> int:
        """Calculate available skill points (1 per level minus unlocked skills)."""
        return self.level - len(self.unlocked_skills)

    @property
    def max_hp(self) -> int:
        """
        Calculate max HP from constitution.
        Uses the Stats.hit_points property which calculates: 10 + (constitution * 2)
        """
        effective_stats = self.get_effective_stats()
        return effective_stats.hit_points

    @property
    def current_hp(self) -> int:
        """
        Get current HP.
        Outside of combat, characters are at full health.
        During combat, this would be tracked separately in the combat state.
        """
        # For now, always return max HP (full health outside combat)
        # TODO: Track combat damage separately when implementing combat system
        return self.max_hp

    def can_afford(self, cost: int) -> bool:
        """Check if character has enough gold."""
        return self.gold >= cost

    def add_gold(self, amount: int) -> None:
        """Add gold to character's wallet."""
        self.gold += amount

    def remove_gold(self, amount: int) -> bool:
        """
        Remove gold from character's wallet.

        Returns:
            True if successful, False if insufficient gold
        """
        if not self.can_afford(amount):
            return False
        self.gold -= amount
        return True

    def add_item(self, item: Item) -> None:
        """Add an item to character's inventory."""
        self.inventory.append(item)

    def remove_item(self, item_id: str) -> Optional[Item]:
        """
        Remove an item from inventory by ID.

        Returns:
            The removed Item or None if not found
        """
        for i, item in enumerate(self.inventory):
            if item.item_id == item_id:
                return self.inventory.pop(i)
        return None

    def equip_item(self, item: Item, slot: str) -> Optional[Item]:
        """
        Equip an item to a specific slot.

        Args:
            item: Item to equip
            slot: Equipment slot ("weapon", "armor", etc.)

        Returns:
            Previously equipped item in that slot (or None)
        """
        # Remove from inventory
        self.remove_item(item.item_id)

        # Unequip current item in slot if present
        previous = self.equipped.get(slot)
        if previous:
            self.add_item(previous)

        # Equip new item
        self.equipped[slot] = item
        return previous

    def unequip_item(self, slot: str) -> Optional[Item]:
        """
        Unequip an item from a slot.

        Args:
            slot: Equipment slot to unequip from

        Returns:
            The unequipped Item or None if slot was empty
        """
        if slot not in self.equipped:
            return None

        item = self.equipped.pop(slot)
        self.add_item(item)
        return item

    def add_experience(self, xp: int) -> bool:
        """
        Add experience points and check for level up.

        Args:
            xp: Amount of experience to add

        Returns:
            True if character leveled up, False otherwise
        """
        self.experience += xp
        required_xp = self._calculate_xp_for_next_level()

        if self.experience >= required_xp:
            self.level_up()
            return True

        return False

    def level_up(self) -> None:
        """
        Level up the character.

        - Increases level
        - Resets experience to overflow amount
        - Could grant stat increases (future enhancement)
        """
        required_xp = self._calculate_xp_for_next_level()
        overflow_xp = self.experience - required_xp

        self.level += 1
        self.experience = overflow_xp

        # Future: Apply stat increases based on class
        # For now, stats are increased manually via skill points

    def _calculate_xp_for_next_level(self) -> int:
        """
        Calculate XP required for next level.

        Formula: 100 * (level ^ 1.5)
        This creates an exponential curve: 100, 282, 519, 800, 1118...

        Returns:
            XP required for next level
        """
        return int(100 * (self.level ** 1.5))

    def to_dict(self) -> Dict[str, Any]:
        """
        Serialize character to dictionary for JSON storage.

        Returns:
            Dictionary containing all character data
        """
        return {
            "character_id": self.character_id,
            "user_id": self.user_id,
            "name": self.name,
            "player_class": self.player_class.to_dict(),
            "origin": self.origin.to_dict(),
            "level": self.level,
            "experience": self.experience,
            "base_stats": self.base_stats.to_dict(),
            "unlocked_skills": self.unlocked_skills,
            "inventory": [item.to_dict() for item in self.inventory],
            "equipped": {slot: item.to_dict() for slot, item in self.equipped.items()},
            "gold": self.gold,
            "active_quests": self.active_quests,
            "discovered_locations": self.discovered_locations,
            "current_location": self.current_location,
            "npc_interactions": self.npc_interactions,
            # Computed properties for AI templates
            "current_hp": self.current_hp,
            "max_hp": self.max_hp,
        }

    def to_story_dict(self) -> Dict[str, Any]:
        """
        Serialize only story-relevant character data for AI prompts.

        This trimmed version reduces token usage by excluding mechanical
        details that aren't needed for narrative generation (IDs, full
        inventory details, skill trees, etc.).

        Returns:
            Dictionary containing story-relevant character data
        """
        effective_stats = self.get_effective_stats()

        # Get equipped item names for context (not full details)
        equipped_summary = {}
        for slot, item in self.equipped.items():
            equipped_summary[slot] = item.name

        # Get skill names from unlocked skills
        skill_names = []
        all_skills = self.player_class.get_all_skills()
        for skill in all_skills:
            if skill.skill_id in self.unlocked_skills:
                skill_names.append({
                    "name": skill.name,
                    "level": 1  # Skills don't have levels, but template expects this
                })

        return {
            "name": self.name,
            "level": self.level,
            "player_class": self.player_class.name,
            "origin_name": self.origin.name,
            "current_hp": self.current_hp,
            "max_hp": self.max_hp,
            "gold": self.gold,
            # Stats for display and checks
            "stats": effective_stats.to_dict(),
            "base_stats": self.base_stats.to_dict(),
            # Simplified collections
            "skills": skill_names,
            "equipped": equipped_summary,
            "inventory_count": len(self.inventory),
            "active_quests_count": len(self.active_quests),
            # Empty list for templates that check completed_quests
            "effects": [],  # Active effects passed separately in combat
        }

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

        Args:
            data: Dictionary containing character data

        Returns:
            Character instance
        """
        from app.models.skills import PlayerClass

        player_class = PlayerClass.from_dict(data["player_class"])
        origin = Origin.from_dict(data["origin"])
        base_stats = Stats.from_dict(data["base_stats"])
        inventory = [Item.from_dict(item) for item in data.get("inventory", [])]
        equipped = {slot: Item.from_dict(item) for slot, item in data.get("equipped", {}).items()}

        return cls(
            character_id=data["character_id"],
            user_id=data["user_id"],
            name=data["name"],
            player_class=player_class,
            origin=origin,
            level=data.get("level", 1),
            experience=data.get("experience", 0),
            base_stats=base_stats,
            unlocked_skills=data.get("unlocked_skills", []),
            inventory=inventory,
            equipped=equipped,
            gold=data.get("gold", 0),
            active_quests=data.get("active_quests", []),
            discovered_locations=data.get("discovered_locations", []),
            current_location=data.get("current_location"),
            npc_interactions=data.get("npc_interactions", {}),
        )

    def __repr__(self) -> str:
        """String representation of the character."""
        return (
            f"Character({self.name}, {self.player_class.name}, "
            f"Lv{self.level}, {self.gold}g)"
        )
