"""Card models for the Mantimon TCG game engine.

This module defines two key model types:
- CardDefinition: Immutable template for a card (the "blueprint")
- CardInstance: Mutable in-game state for a card in play

The separation allows efficient memory usage (definitions are shared) while
maintaining per-card state during gameplay (damage, attached energy, etc.).

Usage:
    # Define a card template
    pikachu = CardDefinition(
        id="pikachu_base_001",
        name="Pikachu",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=60,
        pokemon_type=EnergyType.LIGHTNING,
        attacks=[Attack(name="Thunder Shock", cost=[EnergyType.LIGHTNING], damage=20)],
    )

    # Create an instance for gameplay
    instance = CardInstance(
        instance_id="uuid-here",
        definition_id="pikachu_base_001",
    )

    # Track damage during battle
    instance.damage = 30
"""

from typing import Any

from pydantic import BaseModel, Field, model_validator

from app.core.enums import (
    CardType,
    EnergyType,
    ModifierMode,
    PokemonStage,
    PokemonVariant,
    StatusCondition,
    TrainerType,
)


class Attack(BaseModel):
    """An attack that a Pokemon can use.

    Attacks have an energy cost, base damage, and optional effect.
    Effects are referenced by ID and resolved by the effect handler system.

    Attributes:
        name: Display name of the attack.
        cost: List of energy types required to use this attack.
            Colorless can be satisfied by any energy type.
        damage: Base damage dealt by this attack. May be modified by effects.
        damage_display: UI display string for variable damage (e.g., "30+", "50x").
            Used when damage can vary based on conditions. If None, just show damage value.
        effect_id: Optional reference to an effect handler for special effects.
        effect_params: Parameters passed to the effect handler.
        effect_description: Human-readable description of the effect.
    """

    name: str
    cost: list[EnergyType] = Field(default_factory=list)
    damage: int = 0
    damage_display: str | None = None
    effect_id: str | None = None
    effect_params: dict[str, Any] = Field(default_factory=dict)
    effect_description: str | None = None


class Ability(BaseModel):
    """A special ability that a Pokemon has.

    Abilities can be activated during the main phase (unless specified otherwise).
    Like attacks, abilities reference effect handlers by ID.

    Attributes:
        name: Display name of the ability.
        effect_id: Reference to an effect handler.
        effect_params: Parameters passed to the effect handler.
        effect_description: Human-readable description of the ability.
        uses_per_turn: Maximum uses per turn. None means unlimited uses.
            Default is 1 (once per turn), which matches standard Pokemon TCG rules.
    """

    name: str
    effect_id: str
    effect_params: dict[str, Any] = Field(default_factory=dict)
    effect_description: str | None = None
    uses_per_turn: int | None = 1  # None = unlimited, 1 = once per turn (default)


class WeaknessResistance(BaseModel):
    """Weakness or resistance to a specific energy type.

    This model supports both the global defaults from RulesConfig.combat and
    per-card overrides. If mode/value are not specified, the handler will
    use the game's CombatConfig defaults.

    Attributes:
        energy_type: The energy type this applies to.
        mode: How to apply the modifier (multiplicative or additive).
            If None, uses the game's CombatConfig default for this modifier type.
        value: The modifier value to apply.
            - For multiplicative: damage * value (e.g., 2 for x2)
            - For additive: damage + value (e.g., +20 or -30)
            If None, uses the game's CombatConfig default.

    Examples:
        # Use game defaults (mode and value from CombatConfig)
        WeaknessResistance(energy_type=EnergyType.FIRE)

        # Override just the value (still uses default mode)
        WeaknessResistance(energy_type=EnergyType.FIRE, value=3)  # x3 if default is multiplicative

        # Full override (card-specific behavior)
        WeaknessResistance(
            energy_type=EnergyType.FIRE,
            mode=ModifierMode.ADDITIVE,
            value=40,  # +40 damage instead of x2
        )
    """

    energy_type: EnergyType
    mode: ModifierMode | None = None
    value: int | None = None

    def get_value(self, default: int) -> int:
        """Get the modifier value, falling back to default if not specified.

        Args:
            default: The default value from CombatConfig.

        Returns:
            The value to use for this modifier.
        """
        if self.value is not None:
            return self.value
        return default

    def get_mode(self, default: ModifierMode) -> ModifierMode:
        """Get the modifier mode, falling back to default if not specified.

        Args:
            default: The default mode from CombatConfig.

        Returns:
            The mode to use for this modifier.
        """
        return self.mode if self.mode is not None else default


class CardDefinition(BaseModel):
    """Immutable template for a card.

    This represents the "blueprint" of a card - all the static information
    that doesn't change during gameplay. Multiple CardInstances can reference
    the same CardDefinition.

    The card_type field determines which other fields are relevant:
    - POKEMON: stage, variant, hp, pokemon_type, attacks, abilities, weakness, resistance, retreat_cost
    - TRAINER: trainer_type, effect_id, effect_params
    - ENERGY: energy_type, energy_provides

    Attributes:
        id: Unique identifier for this card definition (e.g., "pikachu_base_001").
        name: Display name of the card.
        card_type: Whether this is a Pokemon, Trainer, or Energy card.
        stage: Evolution stage - BASIC, STAGE_1, or STAGE_2 (Pokemon only).
        variant: Special variant - NORMAL, EX, GX, V, VMAX, VSTAR (Pokemon only).
            Affects knockout points but not evolution mechanics.
        evolves_from: Name of the Pokemon this evolves from (Stage 1/2, VMAX, VSTAR only).
        hp: Hit points (Pokemon only).
        pokemon_type: Energy type of this Pokemon (for weakness/resistance).
        attacks: List of attacks this Pokemon can use.
        abilities: List of abilities this Pokemon has.
        weakness: Weakness to an energy type (Pokemon only).
        resistance: Resistance to an energy type (Pokemon only).
        retreat_cost: Number of energy to discard to retreat (Pokemon only).
        trainer_type: Subtype of trainer card (Trainer only).
        effect_id: Effect handler for this card (Trainer/special Energy).
        effect_params: Parameters for the effect handler.
        effect_description: Human-readable description of the card's effect.
        energy_type: Type of energy this card provides (Energy only).
        energy_provides: List of energy types this card provides.
            Basic energy provides one of its type. Special energy may provide multiple.
        rarity: Card rarity (common, uncommon, rare, etc.).
        set_id: Which card set this belongs to.
        image_url: URL to the card image (CDN).
        image_path: Local path to the card image (e.g., "pokemon/a1/001-bulbasaur.webp").
        illustrator: Artist who illustrated this card.
        flavor_text: Flavor text on the card (Pokemon only, typically).
    """

    id: str
    name: str
    card_type: CardType

    # Pokemon-specific fields
    stage: PokemonStage | None = None
    variant: PokemonVariant = PokemonVariant.NORMAL
    evolves_from: str | None = None
    hp: int | None = None
    pokemon_type: EnergyType | None = None
    attacks: list[Attack] = Field(default_factory=list)
    abilities: list[Ability] = Field(default_factory=list)
    weakness: WeaknessResistance | None = None
    resistance: WeaknessResistance | None = None
    retreat_cost: int = 0

    # Trainer-specific fields
    trainer_type: TrainerType | None = None
    effect_id: str | None = None
    effect_params: dict[str, Any] = Field(default_factory=dict)
    effect_description: str | None = None

    # Energy-specific fields
    energy_type: EnergyType | None = None
    energy_provides: list[EnergyType] = Field(default_factory=list)

    # Metadata
    rarity: str = "common"
    set_id: str = ""
    image_url: str | None = None
    image_path: str | None = None  # Local path: "pokemon/a1/001-bulbasaur.webp"
    illustrator: str | None = None
    flavor_text: str | None = None

    @model_validator(mode="after")
    def validate_card_type_fields(self) -> "CardDefinition":
        """Validate that required fields are set based on card_type.

        Ensures that:
        - Pokemon cards have hp, stage, and pokemon_type
        - Stage 1/2 and VMAX/VSTAR Pokemon have evolves_from
        - Trainer cards have trainer_type
        - Energy cards have energy_type (auto-fills energy_provides if empty)

        Raises:
            ValueError: If required fields are missing for the card type.
        """
        if self.card_type == CardType.POKEMON:
            if self.hp is None:
                raise ValueError("Pokemon cards must have 'hp' set")
            if self.hp <= 0:
                raise ValueError("Pokemon 'hp' must be positive")
            if self.stage is None:
                raise ValueError("Pokemon cards must have 'stage' set")
            if self.pokemon_type is None:
                raise ValueError("Pokemon cards must have 'pokemon_type' set")

            # Evolution chain validation
            if (
                self.stage in (PokemonStage.STAGE_1, PokemonStage.STAGE_2)
                and self.evolves_from is None
            ):
                raise ValueError(f"{self.stage.value} Pokemon must have 'evolves_from' set")
            if (
                self.variant in (PokemonVariant.VMAX, PokemonVariant.VSTAR)
                and self.evolves_from is None
            ):
                raise ValueError(f"{self.variant.value} Pokemon must have 'evolves_from' set")

        elif self.card_type == CardType.TRAINER:
            if self.trainer_type is None:
                raise ValueError("Trainer cards must have 'trainer_type' set")

        elif self.card_type == CardType.ENERGY:
            if self.energy_type is None:
                raise ValueError("Energy cards must have 'energy_type' set")
            # Auto-fill energy_provides if empty
            if not self.energy_provides:
                self.energy_provides = [self.energy_type]

        return self

    def is_pokemon(self) -> bool:
        """Check if this is a Pokemon card."""
        return self.card_type == CardType.POKEMON

    def is_trainer(self) -> bool:
        """Check if this is a Trainer card."""
        return self.card_type == CardType.TRAINER

    def is_energy(self) -> bool:
        """Check if this is an Energy card."""
        return self.card_type == CardType.ENERGY

    def is_basic_pokemon(self) -> bool:
        """Check if this is a Basic Pokemon (can be played directly to bench)."""
        if not self.is_pokemon():
            return False
        return self.stage == PokemonStage.BASIC

    def is_evolution(self) -> bool:
        """Check if this is an evolution Pokemon (Stage 1 or Stage 2)."""
        if not self.is_pokemon():
            return False
        return self.stage in (PokemonStage.STAGE_1, PokemonStage.STAGE_2)

    def requires_evolution_from_variant(self) -> bool:
        """Check if this variant requires evolving from another variant.

        VMAX and VSTAR must evolve from a V Pokemon.
        """
        return self.variant in (PokemonVariant.VMAX, PokemonVariant.VSTAR)

    def knockout_points(self) -> int:
        """Get the default knockout points for this Pokemon based on variant.

        Note: Actual points should come from RulesConfig.prizes.points_for_knockout()
        for configurability. This is a convenience method for the common case.
        """
        if not self.is_pokemon():
            return 0

        points_map = {
            PokemonVariant.NORMAL: 1,
            PokemonVariant.EX: 2,
            PokemonVariant.GX: 2,
            PokemonVariant.V: 2,
            PokemonVariant.VMAX: 3,
            PokemonVariant.VSTAR: 3,
        }
        return points_map.get(self.variant, 1)


class CardInstance(BaseModel):
    """A card instance in play with mutable state.

    While CardDefinition is the template, CardInstance represents a specific
    card during gameplay. It tracks damage, attached energy, status conditions,
    and other per-card state.

    The definition_id links back to the CardDefinition in the game's card registry.

    Stat Modifiers:
        Card effects can modify base stats using the modifier fields. These are
        additive: effective_stat = base_stat + modifier. For example, a Tool card
        that grants +20 HP would set hp_modifier=20.

    Attributes:
        instance_id: Unique identifier for this specific instance (UUID).
        definition_id: Reference to the CardDefinition.id.
        damage: Current damage on this card (Pokemon only).
        attached_energy: Energy cards attached to this Pokemon. These CardInstance
            objects are stored directly on the Pokemon, not in any zone.
        attached_tools: Tool cards attached to this Pokemon. These CardInstance
            objects are stored directly on the Pokemon, not in any zone.
        cards_underneath: Evolution stack - previous evolution stages are stored
            here when a Pokemon evolves. Index 0 is the oldest (Basic), and
            later indices are more recent evolutions. When the Pokemon is knocked
            out, all cards in this stack go to the discard pile.
        status_conditions: Active status conditions on this Pokemon.
        ability_uses_this_turn: Number of times each ability has been used this turn.
            Maps ability index to use count. Compared against Ability.uses_per_turn
            to determine if more uses of that specific ability are allowed.
        hp_modifier: Additive modifier to the Pokemon's base HP. Can be positive
            (e.g., from Tool cards) or negative (e.g., from effects).
        retreat_cost_modifier: Additive modifier to retreat cost. Negative values
            reduce the cost (e.g., Float Stone sets this to -99 to effectively
            make retreat free). Effective retreat cost is clamped to minimum 0.
        damage_modifier: Additive modifier to damage dealt by this Pokemon's attacks.
            Positive values increase damage, negative reduce it.
        attack_cost_overrides: Override the energy cost for specific attacks. Maps
            attack index (0-based) to a new list of EnergyType requirements. If an
            attack index is not in this dict, the base cost from CardDefinition is used.
            Example: {0: [EnergyType.COLORLESS, EnergyType.COLORLESS]} makes the first
            attack cost 2 colorless instead of its normal cost.
        evolved_from_instance_id: The CardInstance this evolved from (deprecated,
            use cards_underneath instead for the full evolution stack).
        turn_played: The turn number when this card was played/evolved.
            Used for evolution timing rules.
        turn_evolved: The turn number when this card last evolved.
            Used to prevent same-turn evolution chains.
    """

    instance_id: str
    definition_id: str

    # Battle state (Pokemon only)
    damage: int = 0
    attached_energy: list["CardInstance"] = Field(default_factory=list)
    attached_tools: list["CardInstance"] = Field(default_factory=list)
    cards_underneath: list["CardInstance"] = Field(default_factory=list)
    status_conditions: list[StatusCondition] = Field(default_factory=list)
    ability_uses_this_turn: dict[int, int] = Field(default_factory=dict)

    # Stat modifiers (applied by card effects, tools, abilities, etc.)
    hp_modifier: int = 0
    retreat_cost_modifier: int = 0
    damage_modifier: int = 0
    attack_cost_overrides: dict[int, list[EnergyType]] = Field(default_factory=dict)

    # Evolution tracking
    evolved_from_instance_id: str | None = None
    turn_played: int | None = None
    turn_evolved: int | None = None

    def effective_hp(self, base_hp: int) -> int:
        """Calculate effective max HP including modifiers.

        Args:
            base_hp: The Pokemon's base HP (from CardDefinition.hp).

        Returns:
            Effective max HP (minimum 1 to prevent instant KO from negative modifiers).
        """
        return max(1, base_hp + self.hp_modifier)

    def effective_retreat_cost(self, base_cost: int) -> int:
        """Calculate effective retreat cost including modifiers.

        Args:
            base_cost: The Pokemon's base retreat cost (from CardDefinition.retreat_cost).

        Returns:
            Effective retreat cost (minimum 0, can't be negative).
        """
        return max(0, base_cost + self.retreat_cost_modifier)

    def effective_attack_cost(
        self, attack_index: int, base_cost: list[EnergyType]
    ) -> list[EnergyType]:
        """Get the effective energy cost for an attack, including any overrides.

        Args:
            attack_index: The index of the attack in CardDefinition.attacks (0-based).
            base_cost: The attack's base energy cost (from Attack.cost).

        Returns:
            The effective energy cost - either the override if set, or the base cost.

        Example:
            # An opponent's effect makes your first attack cost 2 more colorless
            instance.attack_cost_overrides[0] = base_cost + [EnergyType.COLORLESS] * 2

            # A control effect completely changes the cost
            instance.attack_cost_overrides[1] = [EnergyType.PSYCHIC, EnergyType.PSYCHIC]
        """
        if attack_index in self.attack_cost_overrides:
            return self.attack_cost_overrides[attack_index]
        return base_cost

    def is_knocked_out(self, base_hp: int) -> bool:
        """Check if this Pokemon is knocked out.

        Args:
            base_hp: The Pokemon's base HP (from CardDefinition.hp).

        Returns:
            True if damage >= effective HP.
        """
        return self.damage >= self.effective_hp(base_hp)

    def remaining_hp(self, base_hp: int) -> int:
        """Calculate remaining HP.

        Args:
            base_hp: The Pokemon's base HP (from CardDefinition.hp).

        Returns:
            Remaining HP (minimum 0).
        """
        return max(0, self.effective_hp(base_hp) - self.damage)

    def has_status(self, status: StatusCondition) -> bool:
        """Check if this Pokemon has a specific status condition."""
        return status in self.status_conditions

    def add_status(self, status: StatusCondition) -> None:
        """Add a status condition, handling overrides.

        ASLEEP, PARALYZED, and CONFUSED override each other.
        POISONED and BURNED stack with everything.
        """
        # These three override each other
        overriding = {StatusCondition.ASLEEP, StatusCondition.PARALYZED, StatusCondition.CONFUSED}

        if status in overriding:
            # Remove any existing overriding conditions
            self.status_conditions = [s for s in self.status_conditions if s not in overriding]

        if status not in self.status_conditions:
            self.status_conditions.append(status)

    def remove_status(self, status: StatusCondition) -> None:
        """Remove a status condition."""
        if status in self.status_conditions:
            self.status_conditions.remove(status)

    def clear_all_status(self) -> None:
        """Remove all status conditions (e.g., when Pokemon retreats or evolves)."""
        self.status_conditions.clear()

    def reset_turn_state(self) -> None:
        """Reset per-turn state counters. Called at the start of each turn."""
        self.ability_uses_this_turn.clear()

    def can_use_ability(self, ability: Ability, ability_index: int) -> bool:
        """Check if this Pokemon can use the given ability.

        Compares the use count for this specific ability against its uses_per_turn limit.
        If uses_per_turn is None, the ability has no usage limit.

        Args:
            ability: The Ability to check.
            ability_index: The index of the ability in the Pokemon's ability list.

        Returns:
            True if the ability can be used (hasn't hit its per-turn limit).
        """
        if ability.uses_per_turn is None:
            return True  # Unlimited uses
        uses = self.ability_uses_this_turn.get(ability_index, 0)
        return uses < ability.uses_per_turn

    def get_ability_uses(self, ability_index: int) -> int:
        """Get the number of times a specific ability has been used this turn.

        Args:
            ability_index: The index of the ability.

        Returns:
            Number of times the ability has been used this turn.
        """
        return self.ability_uses_this_turn.get(ability_index, 0)

    def increment_ability_uses(self, ability_index: int) -> None:
        """Increment the usage counter for a specific ability.

        Args:
            ability_index: The index of the ability that was used.
        """
        current = self.ability_uses_this_turn.get(ability_index, 0)
        self.ability_uses_this_turn[ability_index] = current + 1

    def can_evolve_this_turn(self, current_turn: int) -> bool:
        """Check if this Pokemon can evolve this turn.

        Pokemon cannot evolve:
        - The same turn they were played
        - The same turn they already evolved

        Args:
            current_turn: The current turn number.

        Returns:
            True if this Pokemon can evolve.
        """
        # Can't evolve same turn as played
        if self.turn_played == current_turn:
            return False
        # Can't evolve same turn as previous evolution
        return self.turn_evolved != current_turn

    def energy_count(self) -> int:
        """Get the number of attached energy cards."""
        return len(self.attached_energy)

    def attach_energy(self, energy_card: "CardInstance") -> None:
        """Attach an energy card to this Pokemon.

        The energy CardInstance is stored directly on this Pokemon, not in any zone.
        When the Pokemon is knocked out, attached energy goes to the owner's discard.

        Args:
            energy_card: The CardInstance of the energy card to attach.
        """
        self.attached_energy.append(energy_card)

    def detach_energy(self, energy_instance_id: str) -> "CardInstance | None":
        """Detach an energy card from this Pokemon.

        Args:
            energy_instance_id: The instance_id of the energy card to detach.

        Returns:
            The detached CardInstance, or None if not found.
        """
        for i, energy in enumerate(self.attached_energy):
            if energy.instance_id == energy_instance_id:
                return self.attached_energy.pop(i)
        return None

    def attach_tool(self, tool_card: "CardInstance") -> None:
        """Attach a tool card to this Pokemon.

        The tool CardInstance is stored directly on this Pokemon, not in any zone.
        When the Pokemon is knocked out, attached tools go to the owner's discard.

        Args:
            tool_card: The CardInstance of the tool card to attach.
        """
        self.attached_tools.append(tool_card)

    def detach_tool(self, tool_instance_id: str) -> "CardInstance | None":
        """Detach a tool card from this Pokemon.

        Args:
            tool_instance_id: The instance_id of the tool card to detach.

        Returns:
            The detached CardInstance, or None if not found.
        """
        for i, tool in enumerate(self.attached_tools):
            if tool.instance_id == tool_instance_id:
                return self.attached_tools.pop(i)
        return None

    def get_all_attached_cards(self) -> list["CardInstance"]:
        """Get all cards attached to this Pokemon.

        Returns:
            List of all attached energy and tool CardInstances.
        """
        return self.attached_energy + self.attached_tools


# Rebuild model to resolve forward references for self-referential types
# (CardInstance contains list[CardInstance] for attached_energy, attached_tools, cards_underneath)
CardInstance.model_rebuild()
