"""Tests for the card models (CardDefinition and CardInstance).

These tests verify:
1. CardDefinition correctly represents all card types
2. CardInstance properly tracks mutable state
3. Helper methods work correctly
4. Status condition logic follows game rules
5. JSON serialization round-trips work
"""

from app.core.enums import (
    CardType,
    EnergyType,
    ModifierMode,
    PokemonStage,
    PokemonVariant,
    StatusCondition,
    TrainerType,
)
from app.core.models.card import (
    Ability,
    Attack,
    CardDefinition,
    CardInstance,
    WeaknessResistance,
)


class TestAttack:
    """Tests for the Attack model."""

    def test_basic_attack(self) -> None:
        """
        Verify a basic attack can be created with minimal fields.
        """
        attack = Attack(name="Tackle", damage=10)

        assert attack.name == "Tackle"
        assert attack.damage == 10
        assert attack.cost == []
        assert attack.effect_id is None

    def test_attack_with_energy_cost(self) -> None:
        """
        Verify attacks can have energy costs.
        """
        attack = Attack(
            name="Thunder Shock",
            cost=[EnergyType.LIGHTNING],
            damage=20,
        )

        assert attack.cost == [EnergyType.LIGHTNING]

    def test_attack_with_colorless_cost(self) -> None:
        """
        Verify attacks can have colorless energy requirements.

        Colorless can be satisfied by any energy type.
        """
        attack = Attack(
            name="Slash",
            cost=[EnergyType.COLORLESS, EnergyType.COLORLESS],
            damage=30,
        )

        assert len(attack.cost) == 2
        assert all(c == EnergyType.COLORLESS for c in attack.cost)

    def test_attack_with_effect(self) -> None:
        """
        Verify attacks can reference effect handlers.
        """
        attack = Attack(
            name="Thunder Shock",
            cost=[EnergyType.LIGHTNING],
            damage=20,
            effect_id="may_paralyze",
            effect_params={"chance": 0.5},
            effect_description="Flip a coin. If heads, the Defending Pokemon is now Paralyzed.",
        )

        assert attack.effect_id == "may_paralyze"
        assert attack.effect_params["chance"] == 0.5

    def test_attack_json_round_trip(self) -> None:
        """
        Verify Attack serializes and deserializes correctly.
        """
        original = Attack(
            name="Thunderbolt",
            cost=[EnergyType.LIGHTNING, EnergyType.LIGHTNING, EnergyType.COLORLESS],
            damage=100,
            effect_id="discard_all_energy",
        )

        json_str = original.model_dump_json()
        restored = Attack.model_validate_json(json_str)

        assert restored.name == original.name
        assert restored.cost == original.cost
        assert restored.damage == original.damage
        assert restored.effect_id == original.effect_id


class TestAbility:
    """Tests for the Ability model."""

    def test_basic_ability(self) -> None:
        """
        Verify an ability can be created with default uses_per_turn.

        Default is 1 use per turn, matching standard Pokemon TCG rules.
        """
        ability = Ability(
            name="Rain Dish",
            effect_id="heal_between_turns",
            effect_params={"amount": 10},
        )

        assert ability.name == "Rain Dish"
        assert ability.effect_id == "heal_between_turns"
        assert ability.uses_per_turn == 1  # Default: once per turn

    def test_ability_unlimited_uses(self) -> None:
        """
        Verify abilities can be configured for unlimited uses per turn.

        Setting uses_per_turn to None allows the ability to be used
        any number of times per turn.
        """
        ability = Ability(
            name="Energy Transfer",
            effect_id="move_energy",
            uses_per_turn=None,
        )

        assert ability.uses_per_turn is None

    def test_ability_multiple_uses_per_turn(self) -> None:
        """
        Verify abilities can have custom uses_per_turn limit.

        Some abilities may be usable 2 or more times per turn.
        """
        ability = Ability(
            name="Double Draw",
            effect_id="draw_cards",
            uses_per_turn=2,
        )

        assert ability.uses_per_turn == 2


class TestWeaknessResistance:
    """Tests for the WeaknessResistance model."""

    def test_weakness(self) -> None:
        """
        Verify weakness can be defined with multiplicative mode.
        """
        weakness = WeaknessResistance(
            energy_type=EnergyType.FIGHTING,
            mode=ModifierMode.MULTIPLICATIVE,
            value=2,
        )

        assert weakness.energy_type == EnergyType.FIGHTING
        assert weakness.mode == ModifierMode.MULTIPLICATIVE
        assert weakness.value == 2

    def test_resistance(self) -> None:
        """
        Verify resistance can be defined with additive mode.
        """
        resistance = WeaknessResistance(
            energy_type=EnergyType.METAL,
            mode=ModifierMode.ADDITIVE,
            value=-30,
        )

        assert resistance.energy_type == EnergyType.METAL
        assert resistance.mode == ModifierMode.ADDITIVE
        assert resistance.value == -30


class TestCardDefinitionPokemon:
    """Tests for Pokemon CardDefinitions."""

    def test_basic_pokemon(self) -> None:
        """
        Verify a basic Pokemon can be created with all relevant fields.

        Basic Pokemon can be played directly from hand to bench.
        Default variant is NORMAL (worth 1 knockout point).
        """
        pikachu = CardDefinition(
            id="pikachu_base_001",
            name="Pikachu",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=60,
            pokemon_type=EnergyType.LIGHTNING,
            attacks=[
                Attack(name="Gnaw", damage=10),
                Attack(
                    name="Thunder Shock",
                    cost=[EnergyType.LIGHTNING],
                    damage=20,
                    effect_id="may_paralyze",
                ),
            ],
            weakness=WeaknessResistance(
                energy_type=EnergyType.FIGHTING, mode=ModifierMode.MULTIPLICATIVE, value=2
            ),
            retreat_cost=1,
        )

        assert pikachu.id == "pikachu_base_001"
        assert pikachu.name == "Pikachu"
        assert pikachu.is_pokemon() is True
        assert pikachu.is_basic_pokemon() is True
        assert pikachu.is_evolution() is False
        assert pikachu.hp == 60
        assert len(pikachu.attacks) == 2
        assert pikachu.variant == PokemonVariant.NORMAL
        assert pikachu.knockout_points() == 1

    def test_stage_1_pokemon(self) -> None:
        """
        Verify a Stage 1 Pokemon can be created.

        Stage 1 Pokemon must evolve from a Basic Pokemon.
        """
        raichu = CardDefinition(
            id="raichu_base_001",
            name="Raichu",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_1,
            evolves_from="Pikachu",
            hp=90,
            pokemon_type=EnergyType.LIGHTNING,
            attacks=[Attack(name="Thunder", cost=[EnergyType.LIGHTNING] * 3, damage=60)],
        )

        assert raichu.stage == PokemonStage.STAGE_1
        assert raichu.evolves_from == "Pikachu"
        assert raichu.is_evolution() is True
        assert raichu.is_basic_pokemon() is False
        assert raichu.variant == PokemonVariant.NORMAL
        assert raichu.knockout_points() == 1

    def test_stage_2_pokemon(self) -> None:
        """
        Verify a Stage 2 Pokemon can be created.

        Stage 2 Pokemon must evolve from a Stage 1 Pokemon.
        """
        charizard = CardDefinition(
            id="charizard_base_001",
            name="Charizard",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_2,
            evolves_from="Charmeleon",
            hp=120,
            pokemon_type=EnergyType.FIRE,
        )

        assert charizard.stage == PokemonStage.STAGE_2
        assert charizard.is_evolution() is True
        assert charizard.variant == PokemonVariant.NORMAL
        assert charizard.knockout_points() == 1

    def test_basic_v_pokemon(self) -> None:
        """
        Verify a Basic V Pokemon can be created.

        V is a variant, not a stage. V Pokemon can be any stage but are typically Basic.
        V Pokemon are worth 2 knockout points.
        """
        pikachu_v = CardDefinition(
            id="pikachu_v_001",
            name="Pikachu V",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.V,
            hp=190,
            pokemon_type=EnergyType.LIGHTNING,
        )

        assert pikachu_v.stage == PokemonStage.BASIC
        assert pikachu_v.variant == PokemonVariant.V
        assert pikachu_v.is_basic_pokemon() is True
        assert pikachu_v.is_evolution() is False
        assert pikachu_v.knockout_points() == 2

    def test_basic_vmax_pokemon(self) -> None:
        """
        Verify a VMAX Pokemon requires evolution from V.

        VMAX always evolves from a V variant. The stage is still BASIC
        (since V is typically Basic), but the VMAX variant indicates
        it must evolve from a V Pokemon.
        """
        pikachu_vmax = CardDefinition(
            id="pikachu_vmax_001",
            name="Pikachu VMAX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.VMAX,
            evolves_from="Pikachu V",
            hp=310,
            pokemon_type=EnergyType.LIGHTNING,
        )

        assert pikachu_vmax.stage == PokemonStage.BASIC
        assert pikachu_vmax.variant == PokemonVariant.VMAX
        # VMAX is technically BASIC stage but requires evolution from V
        assert pikachu_vmax.is_basic_pokemon() is True
        assert pikachu_vmax.requires_evolution_from_variant() is True
        assert pikachu_vmax.knockout_points() == 3

    def test_basic_vstar_pokemon(self) -> None:
        """
        Verify a VSTAR Pokemon requires evolution from V.

        VSTAR always evolves from a V variant, worth 2 knockout points.
        """
        pikachu_vstar = CardDefinition(
            id="pikachu_vstar_001",
            name="Pikachu VSTAR",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.VSTAR,
            evolves_from="Pikachu V",
            hp=280,
            pokemon_type=EnergyType.LIGHTNING,
        )

        assert pikachu_vstar.variant == PokemonVariant.VSTAR
        assert pikachu_vstar.requires_evolution_from_variant() is True
        assert pikachu_vstar.knockout_points() == 3

    def test_basic_ex_pokemon(self) -> None:
        """
        Verify a Basic EX Pokemon can be created.

        EX is a variant, not a stage. Basic EX Pokemon can be played
        directly and are worth 2 knockout points.
        """
        mewtwo_ex = CardDefinition(
            id="mewtwo_ex_001",
            name="Mewtwo EX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.EX,
            hp=170,
            pokemon_type=EnergyType.PSYCHIC,
        )

        assert mewtwo_ex.stage == PokemonStage.BASIC
        assert mewtwo_ex.variant == PokemonVariant.EX
        assert mewtwo_ex.is_basic_pokemon() is True
        assert mewtwo_ex.knockout_points() == 2

    def test_stage_2_ex_pokemon(self) -> None:
        """
        Verify a Stage 2 EX Pokemon can be created.

        Stage and variant are orthogonal. A Stage 2 EX must evolve from
        a Stage 1 and is worth 2 knockout points due to being EX.
        """
        charizard_ex = CardDefinition(
            id="charizard_ex_001",
            name="Charizard EX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_2,
            variant=PokemonVariant.EX,
            evolves_from="Charmeleon",
            hp=250,
            pokemon_type=EnergyType.FIRE,
        )

        assert charizard_ex.stage == PokemonStage.STAGE_2
        assert charizard_ex.variant == PokemonVariant.EX
        assert charizard_ex.is_basic_pokemon() is False
        assert charizard_ex.is_evolution() is True
        assert charizard_ex.knockout_points() == 2

    def test_basic_gx_pokemon(self) -> None:
        """
        Verify a Basic GX Pokemon can be created.

        GX is a variant. Basic GX can be played directly and are worth
        2 knockout points.
        """
        tapu_lele_gx = CardDefinition(
            id="tapu_lele_gx_001",
            name="Tapu Lele GX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.GX,
            hp=170,
            pokemon_type=EnergyType.PSYCHIC,
        )

        assert tapu_lele_gx.stage == PokemonStage.BASIC
        assert tapu_lele_gx.variant == PokemonVariant.GX
        assert tapu_lele_gx.is_basic_pokemon() is True
        assert tapu_lele_gx.knockout_points() == 2

    def test_stage_1_gx_pokemon(self) -> None:
        """
        Verify a Stage 1 GX Pokemon can be created.

        Stage 1 GX must evolve from a Basic and are worth 2 points.
        """
        raichu_gx = CardDefinition(
            id="raichu_gx_001",
            name="Raichu GX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_1,
            variant=PokemonVariant.GX,
            evolves_from="Pikachu",
            hp=210,
            pokemon_type=EnergyType.LIGHTNING,
        )

        assert raichu_gx.stage == PokemonStage.STAGE_1
        assert raichu_gx.variant == PokemonVariant.GX
        assert raichu_gx.is_basic_pokemon() is False
        assert raichu_gx.is_evolution() is True
        assert raichu_gx.evolves_from == "Pikachu"
        assert raichu_gx.knockout_points() == 2

    def test_knockout_points_for_all_variants(self) -> None:
        """
        Verify knockout_points returns correct values for all variants.

        Knockout points are determined by variant, not stage.
        """
        variants_and_points = [
            (PokemonVariant.NORMAL, 1),
            (PokemonVariant.EX, 2),
            (PokemonVariant.GX, 2),
            (PokemonVariant.V, 2),
            (PokemonVariant.VMAX, 3),
            (PokemonVariant.VSTAR, 3),
        ]

        for variant, expected_points in variants_and_points:
            # VMAX and VSTAR require evolves_from
            evolves_from = (
                "Pikachu V" if variant in (PokemonVariant.VMAX, PokemonVariant.VSTAR) else None
            )
            card = CardDefinition(
                id=f"test_{variant.value}",
                name="Test Pokemon",
                card_type=CardType.POKEMON,
                stage=PokemonStage.BASIC,
                variant=variant,
                hp=100,
                pokemon_type=EnergyType.COLORLESS,
                evolves_from=evolves_from,
            )
            assert card.knockout_points() == expected_points, f"Failed for {variant}"

    def test_requires_evolution_from_variant(self) -> None:
        """
        Verify requires_evolution_from_variant for VMAX and VSTAR.

        VMAX and VSTAR must evolve from V Pokemon, regardless of evolution stage.
        """
        # VMAX requires evolution from V
        vmax = CardDefinition(
            id="test_vmax",
            name="Test VMAX",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.VMAX,
            hp=300,
            pokemon_type=EnergyType.LIGHTNING,
            evolves_from="Pikachu V",
        )
        assert vmax.requires_evolution_from_variant() is True

        # VSTAR requires evolution from V
        vstar = CardDefinition(
            id="test_vstar",
            name="Test VSTAR",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.VSTAR,
            hp=250,
            pokemon_type=EnergyType.LIGHTNING,
            evolves_from="Pikachu V",
        )
        assert vstar.requires_evolution_from_variant() is True

        # Other variants don't require V evolution
        for variant in [
            PokemonVariant.NORMAL,
            PokemonVariant.EX,
            PokemonVariant.GX,
            PokemonVariant.V,
        ]:
            card = CardDefinition(
                id=f"test_{variant.value}",
                name="Test",
                card_type=CardType.POKEMON,
                stage=PokemonStage.BASIC,
                variant=variant,
                hp=100,
                pokemon_type=EnergyType.COLORLESS,
            )
            assert card.requires_evolution_from_variant() is False, f"Failed for {variant}"

    def test_stage_determines_playability(self) -> None:
        """
        Verify that stage (not variant) determines direct playability.

        is_basic_pokemon checks only the stage, not the variant.
        """
        # Basic stage = playable directly
        basic_normal = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.NORMAL,
            hp=50,
            pokemon_type=EnergyType.COLORLESS,
        )
        basic_ex = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.EX,
            hp=150,
            pokemon_type=EnergyType.COLORLESS,
        )
        assert basic_normal.is_basic_pokemon() is True
        assert basic_ex.is_basic_pokemon() is True

        # Stage 1 = must evolve
        stage1_normal = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_1,
            variant=PokemonVariant.NORMAL,
            hp=80,
            pokemon_type=EnergyType.COLORLESS,
            evolves_from="Basic",
        )
        stage1_gx = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_1,
            variant=PokemonVariant.GX,
            hp=200,
            pokemon_type=EnergyType.COLORLESS,
            evolves_from="Basic",
        )
        assert stage1_normal.is_basic_pokemon() is False
        assert stage1_gx.is_basic_pokemon() is False

    def test_default_variant_is_normal(self) -> None:
        """
        Verify that Pokemon without explicit variant default to NORMAL.
        """
        pokemon = CardDefinition(
            id="test",
            name="Test Pokemon",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=50,
            pokemon_type=EnergyType.COLORLESS,
        )
        assert pokemon.variant == PokemonVariant.NORMAL


class TestCardDefinitionTrainer:
    """Tests for Trainer CardDefinitions."""

    def test_item_card(self) -> None:
        """
        Verify an Item trainer card can be created.
        """
        potion = CardDefinition(
            id="potion_base_001",
            name="Potion",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.ITEM,
            effect_id="heal",
            effect_params={"amount": 30},
            effect_description="Heal 30 damage from one of your Pokemon.",
        )

        assert potion.is_trainer() is True
        assert potion.trainer_type == TrainerType.ITEM
        assert potion.effect_id == "heal"

    def test_supporter_card(self) -> None:
        """
        Verify a Supporter trainer card can be created.
        """
        professor = CardDefinition(
            id="professor_oak_001",
            name="Professor Oak",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.SUPPORTER,
            effect_id="discard_hand_draw",
            effect_params={"draw_count": 7},
        )

        assert professor.trainer_type == TrainerType.SUPPORTER

    def test_stadium_card(self) -> None:
        """
        Verify a Stadium trainer card can be created.
        """
        stadium = CardDefinition(
            id="pokemon_center_001",
            name="Pokemon Center",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.STADIUM,
            effect_id="stadium_heal_between_turns",
        )

        assert stadium.trainer_type == TrainerType.STADIUM

    def test_tool_card(self) -> None:
        """
        Verify a Tool trainer card can be created.
        """
        tool = CardDefinition(
            id="lucky_egg_001",
            name="Lucky Egg",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.TOOL,
            effect_id="draw_on_knockout",
        )

        assert tool.trainer_type == TrainerType.TOOL


class TestCardDefinitionEnergy:
    """Tests for Energy CardDefinitions."""

    def test_basic_energy(self) -> None:
        """
        Verify a basic energy card can be created.
        """
        fire_energy = CardDefinition(
            id="fire_energy_001",
            name="Fire Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.FIRE,
            energy_provides=[EnergyType.FIRE],
        )

        assert fire_energy.is_energy() is True
        assert fire_energy.energy_type == EnergyType.FIRE
        assert fire_energy.energy_provides == [EnergyType.FIRE]

    def test_special_energy(self) -> None:
        """
        Verify a special energy card providing multiple types.
        """
        rainbow = CardDefinition(
            id="rainbow_energy_001",
            name="Rainbow Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.COLORLESS,  # Base type for special energy
            energy_provides=list(EnergyType),  # Provides all types
            effect_id="rainbow_damage",
            effect_params={"damage_on_attach": 10},
        )

        assert len(rainbow.energy_provides) == len(EnergyType)
        assert rainbow.effect_id == "rainbow_damage"


class TestCardDefinitionValidation:
    """Tests for CardDefinition field validation.

    These tests verify that the model_validator correctly enforces required
    fields based on card type. Each test uses pytest.raises to confirm that
    invalid card definitions are rejected at construction time.

    This explicit negative testing ensures validation rules are documented
    and protected against regression.
    """

    import pytest

    def test_pokemon_without_hp_raises(self) -> None:
        """
        Verify Pokemon cards must have hp field set.

        Pokemon without HP would break damage calculations and knockout detection.
        """
        import pytest

        with pytest.raises(ValueError, match="hp"):
            CardDefinition(
                id="invalid-pokemon",
                name="Invalid",
                card_type=CardType.POKEMON,
                stage=PokemonStage.BASIC,
                pokemon_type=EnergyType.FIRE,
                # hp is missing
            )

    def test_pokemon_with_zero_hp_raises(self) -> None:
        """
        Verify Pokemon cards must have positive hp.

        Zero HP Pokemon would be instantly knocked out, which is invalid.
        """
        import pytest

        with pytest.raises(ValueError, match="positive"):
            CardDefinition(
                id="invalid-pokemon",
                name="Invalid",
                card_type=CardType.POKEMON,
                stage=PokemonStage.BASIC,
                pokemon_type=EnergyType.FIRE,
                hp=0,
            )

    def test_pokemon_with_negative_hp_raises(self) -> None:
        """
        Verify Pokemon cards reject negative hp values.

        Negative HP is nonsensical and would break game logic.
        """
        import pytest

        with pytest.raises(ValueError, match="positive"):
            CardDefinition(
                id="invalid-pokemon",
                name="Invalid",
                card_type=CardType.POKEMON,
                stage=PokemonStage.BASIC,
                pokemon_type=EnergyType.FIRE,
                hp=-10,
            )

    def test_pokemon_without_stage_raises(self) -> None:
        """
        Verify Pokemon cards must have stage field set.

        Stage is required for evolution chain validation and gameplay rules.
        """
        import pytest

        with pytest.raises(ValueError, match="stage"):
            CardDefinition(
                id="invalid-pokemon",
                name="Invalid",
                card_type=CardType.POKEMON,
                hp=60,
                pokemon_type=EnergyType.FIRE,
                # stage is missing
            )

    def test_pokemon_without_type_raises(self) -> None:
        """
        Verify Pokemon cards must have pokemon_type field set.

        Type is required for weakness/resistance calculations.
        """
        import pytest

        with pytest.raises(ValueError, match="pokemon_type"):
            CardDefinition(
                id="invalid-pokemon",
                name="Invalid",
                card_type=CardType.POKEMON,
                hp=60,
                stage=PokemonStage.BASIC,
                # pokemon_type is missing
            )

    def test_stage_1_without_evolves_from_raises(self) -> None:
        """
        Verify Stage 1 Pokemon must specify what they evolve from.

        Stage 1 Pokemon must have evolves_from to validate evolution chains.
        """
        import pytest

        with pytest.raises(ValueError, match="evolves_from"):
            CardDefinition(
                id="invalid-stage1",
                name="Invalid Stage 1",
                card_type=CardType.POKEMON,
                hp=90,
                stage=PokemonStage.STAGE_1,
                pokemon_type=EnergyType.FIRE,
                # evolves_from is missing
            )

    def test_stage_2_without_evolves_from_raises(self) -> None:
        """
        Verify Stage 2 Pokemon must specify what they evolve from.

        Stage 2 Pokemon must have evolves_from to validate evolution chains.
        """
        import pytest

        with pytest.raises(ValueError, match="evolves_from"):
            CardDefinition(
                id="invalid-stage2",
                name="Invalid Stage 2",
                card_type=CardType.POKEMON,
                hp=150,
                stage=PokemonStage.STAGE_2,
                pokemon_type=EnergyType.FIRE,
                # evolves_from is missing
            )

    def test_vmax_without_evolves_from_raises(self) -> None:
        """
        Verify VMAX Pokemon must specify what they evolve from.

        VMAX are evolution variants that require a base V Pokemon.
        """
        import pytest

        with pytest.raises(ValueError, match="evolves_from"):
            CardDefinition(
                id="invalid-vmax",
                name="Invalid VMAX",
                card_type=CardType.POKEMON,
                hp=320,
                stage=PokemonStage.BASIC,  # VMAX can be BASIC stage but still evolves
                variant=PokemonVariant.VMAX,
                pokemon_type=EnergyType.FIRE,
                # evolves_from is missing
            )

    def test_vstar_without_evolves_from_raises(self) -> None:
        """
        Verify VSTAR Pokemon must specify what they evolve from.

        VSTAR are evolution variants that require a base V Pokemon.
        """
        import pytest

        with pytest.raises(ValueError, match="evolves_from"):
            CardDefinition(
                id="invalid-vstar",
                name="Invalid VSTAR",
                card_type=CardType.POKEMON,
                hp=280,
                stage=PokemonStage.BASIC,
                variant=PokemonVariant.VSTAR,
                pokemon_type=EnergyType.FIRE,
                # evolves_from is missing
            )

    def test_trainer_without_trainer_type_raises(self) -> None:
        """
        Verify Trainer cards must have trainer_type field set.

        Trainer type (Item, Supporter, Stadium) determines gameplay rules.
        """
        import pytest

        with pytest.raises(ValueError, match="trainer_type"):
            CardDefinition(
                id="invalid-trainer",
                name="Invalid Trainer",
                card_type=CardType.TRAINER,
                # trainer_type is missing
            )

    def test_energy_without_energy_type_raises(self) -> None:
        """
        Verify Energy cards must have energy_type field set.

        Energy type determines what attack costs the energy can satisfy.
        """
        import pytest

        with pytest.raises(ValueError, match="energy_type"):
            CardDefinition(
                id="invalid-energy",
                name="Invalid Energy",
                card_type=CardType.ENERGY,
                # energy_type is missing
            )

    def test_valid_basic_pokemon_passes(self) -> None:
        """
        Verify valid Basic Pokemon passes validation (sanity check).

        Ensures validation doesn't reject valid cards.
        """
        pokemon = CardDefinition(
            id="valid-pokemon",
            name="Valid Pokemon",
            card_type=CardType.POKEMON,
            hp=60,
            stage=PokemonStage.BASIC,
            pokemon_type=EnergyType.FIRE,
        )

        assert pokemon.hp == 60
        assert pokemon.stage == PokemonStage.BASIC
        assert pokemon.pokemon_type == EnergyType.FIRE

    def test_valid_stage_1_with_evolves_from_passes(self) -> None:
        """
        Verify valid Stage 1 Pokemon with evolves_from passes validation.
        """
        pokemon = CardDefinition(
            id="valid-stage1",
            name="Valid Stage 1",
            card_type=CardType.POKEMON,
            hp=90,
            stage=PokemonStage.STAGE_1,
            pokemon_type=EnergyType.FIRE,
            evolves_from="charmander",
        )

        assert pokemon.stage == PokemonStage.STAGE_1
        assert pokemon.evolves_from == "charmander"

    def test_valid_vmax_with_evolves_from_passes(self) -> None:
        """
        Verify valid VMAX Pokemon with evolves_from passes validation.
        """
        pokemon = CardDefinition(
            id="valid-vmax",
            name="Valid VMAX",
            card_type=CardType.POKEMON,
            hp=320,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.VMAX,
            pokemon_type=EnergyType.FIRE,
            evolves_from="charizard-v",
        )

        assert pokemon.variant == PokemonVariant.VMAX
        assert pokemon.evolves_from == "charizard-v"


class TestCardDefinitionHelperMethodsOnNonPokemon:
    """Tests for Pokemon-specific helper methods when called on non-Pokemon cards.

    These tests verify defensive behavior when helper methods that are designed
    for Pokemon cards are called on Trainer or Energy cards.
    """

    def test_is_basic_pokemon_returns_false_for_trainer(self) -> None:
        """
        Verify is_basic_pokemon returns False for Trainer cards.

        Even though Trainers don't have a stage, they are not Basic Pokemon.
        """
        trainer = CardDefinition(
            id="potion_001",
            name="Potion",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.ITEM,
        )

        assert trainer.is_basic_pokemon() is False

    def test_is_basic_pokemon_returns_false_for_energy(self) -> None:
        """
        Verify is_basic_pokemon returns False for Energy cards.
        """
        energy = CardDefinition(
            id="fire_energy_001",
            name="Fire Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.FIRE,
            energy_provides=[EnergyType.FIRE],
        )

        assert energy.is_basic_pokemon() is False

    def test_is_evolution_returns_false_for_trainer(self) -> None:
        """
        Verify is_evolution returns False for Trainer cards.

        Trainers don't evolve, so this should always be False.
        """
        trainer = CardDefinition(
            id="professor_001",
            name="Professor Oak",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.SUPPORTER,
        )

        assert trainer.is_evolution() is False

    def test_is_evolution_returns_false_for_energy(self) -> None:
        """
        Verify is_evolution returns False for Energy cards.
        """
        energy = CardDefinition(
            id="lightning_energy_001",
            name="Lightning Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.LIGHTNING,
            energy_provides=[EnergyType.LIGHTNING],
        )

        assert energy.is_evolution() is False

    def test_knockout_points_returns_zero_for_trainer(self) -> None:
        """
        Verify knockout_points returns 0 for Trainer cards.

        Trainers can't be knocked out, so they have no knockout points.
        """
        trainer = CardDefinition(
            id="switch_001",
            name="Switch",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.ITEM,
        )

        assert trainer.knockout_points() == 0

    def test_knockout_points_returns_zero_for_energy(self) -> None:
        """
        Verify knockout_points returns 0 for Energy cards.

        Energy cards can't be knocked out.
        """
        energy = CardDefinition(
            id="psychic_energy_001",
            name="Psychic Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.PSYCHIC,
            energy_provides=[EnergyType.PSYCHIC],
        )

        assert energy.knockout_points() == 0


class TestCardDefinitionHelpers:
    """Tests for CardDefinition helper methods."""

    def test_is_pokemon(self) -> None:
        """Verify is_pokemon() returns correct value."""
        pokemon = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=60,
            pokemon_type=EnergyType.COLORLESS,
        )
        trainer = CardDefinition(
            id="test", name="Test", card_type=CardType.TRAINER, trainer_type=TrainerType.ITEM
        )
        energy = CardDefinition(
            id="test", name="Test", card_type=CardType.ENERGY, energy_type=EnergyType.COLORLESS
        )

        assert pokemon.is_pokemon() is True
        assert trainer.is_pokemon() is False
        assert energy.is_pokemon() is False

    def test_is_trainer(self) -> None:
        """Verify is_trainer() returns correct value."""
        pokemon = CardDefinition(
            id="test",
            name="Test",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=60,
            pokemon_type=EnergyType.COLORLESS,
        )
        trainer = CardDefinition(
            id="test", name="Test", card_type=CardType.TRAINER, trainer_type=TrainerType.ITEM
        )

        assert pokemon.is_trainer() is False
        assert trainer.is_trainer() is True

    def test_is_energy(self) -> None:
        """Verify is_energy() returns correct value."""
        energy = CardDefinition(
            id="test", name="Test", card_type=CardType.ENERGY, energy_type=EnergyType.COLORLESS
        )

        assert energy.is_energy() is True


class TestCardDefinitionJsonRoundTrip:
    """Tests for JSON serialization of CardDefinition."""

    def test_pokemon_round_trip(self) -> None:
        """
        Verify a complex Pokemon definition round-trips through JSON.
        """
        original = 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),
            ],
            abilities=[
                Ability(name="Static", effect_id="static_paralysis"),
            ],
            weakness=WeaknessResistance(
                energy_type=EnergyType.FIGHTING, mode=ModifierMode.MULTIPLICATIVE, value=2
            ),
            resistance=WeaknessResistance(
                energy_type=EnergyType.METAL, mode=ModifierMode.ADDITIVE, value=-30
            ),
            retreat_cost=1,
            rarity="common",
            set_id="base",
        )

        json_str = original.model_dump_json()
        restored = CardDefinition.model_validate_json(json_str)

        assert restored.id == original.id
        assert restored.name == original.name
        assert restored.hp == original.hp
        assert len(restored.attacks) == 1
        assert restored.attacks[0].name == "Thunder Shock"
        assert restored.weakness is not None
        assert restored.weakness.energy_type == EnergyType.FIGHTING


class TestCardInstance:
    """Tests for CardInstance."""

    def test_basic_instance(self) -> None:
        """
        Verify a card instance can be created with minimal fields.
        """
        instance = CardInstance(
            instance_id="uuid-12345",
            definition_id="pikachu_base_001",
        )

        assert instance.instance_id == "uuid-12345"
        assert instance.definition_id == "pikachu_base_001"
        assert instance.damage == 0
        assert instance.attached_energy == []
        assert instance.status_conditions == []

    def test_damage_tracking(self) -> None:
        """
        Verify damage can be tracked on a card instance.
        """
        instance = CardInstance(
            instance_id="uuid-12345",
            definition_id="pikachu_base_001",
        )

        instance.damage = 30
        assert instance.damage == 30

        instance.damage += 20
        assert instance.damage == 50

    def test_is_knocked_out(self) -> None:
        """
        Verify knockout detection works correctly.
        """
        instance = CardInstance(
            instance_id="uuid-12345",
            definition_id="pikachu_base_001",
        )

        hp = 60
        instance.damage = 50
        assert instance.is_knocked_out(hp) is False

        instance.damage = 60
        assert instance.is_knocked_out(hp) is True

        instance.damage = 100  # Overkill
        assert instance.is_knocked_out(hp) is True

    def test_remaining_hp(self) -> None:
        """
        Verify remaining HP calculation.
        """
        instance = CardInstance(
            instance_id="uuid-12345",
            definition_id="pikachu_base_001",
        )

        hp = 60
        assert instance.remaining_hp(hp) == 60

        instance.damage = 30
        assert instance.remaining_hp(hp) == 30

        instance.damage = 100
        assert instance.remaining_hp(hp) == 0  # Minimum 0


class TestCardInstanceStatModifiers:
    """Tests for stat modifier functionality on CardInstance."""

    def test_effective_hp_no_modifier(self) -> None:
        """
        Verify effective_hp returns base HP when no modifier is set.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_hp = 60

        assert instance.effective_hp(base_hp) == 60

    def test_effective_hp_positive_modifier(self) -> None:
        """
        Verify effective_hp adds positive modifiers (e.g., from Tool cards).

        Example: A Tool card like "Giant Cape" grants +20 HP.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.hp_modifier = 20
        base_hp = 60

        assert instance.effective_hp(base_hp) == 80

    def test_effective_hp_negative_modifier(self) -> None:
        """
        Verify effective_hp subtracts negative modifiers.

        Some effects may reduce a Pokemon's max HP.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.hp_modifier = -20
        base_hp = 60

        assert instance.effective_hp(base_hp) == 40

    def test_effective_hp_minimum_is_one(self) -> None:
        """
        Verify effective_hp never goes below 1.

        This prevents instant KO from extreme negative modifiers.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.hp_modifier = -100  # More than base HP
        base_hp = 60

        assert instance.effective_hp(base_hp) == 1

    def test_is_knocked_out_with_hp_modifier(self) -> None:
        """
        Verify is_knocked_out uses effective HP including modifiers.

        A Pokemon with +20 HP modifier should survive 60 damage
        when base HP is 60.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.hp_modifier = 20
        base_hp = 60

        instance.damage = 60
        assert instance.is_knocked_out(base_hp) is False  # Effective HP is 80

        instance.damage = 80
        assert instance.is_knocked_out(base_hp) is True

    def test_remaining_hp_with_hp_modifier(self) -> None:
        """
        Verify remaining_hp uses effective HP including modifiers.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.hp_modifier = 20
        base_hp = 60

        assert instance.remaining_hp(base_hp) == 80  # Effective HP

        instance.damage = 30
        assert instance.remaining_hp(base_hp) == 50

    def test_effective_retreat_cost_no_modifier(self) -> None:
        """
        Verify effective_retreat_cost returns base cost when no modifier is set.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_cost = 2

        assert instance.effective_retreat_cost(base_cost) == 2

    def test_effective_retreat_cost_negative_modifier(self) -> None:
        """
        Verify effective_retreat_cost with negative modifier (reduces cost).

        Example: An ability or Tool that reduces retreat cost by 1.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.retreat_cost_modifier = -1
        base_cost = 2

        assert instance.effective_retreat_cost(base_cost) == 1

    def test_effective_retreat_cost_free_retreat(self) -> None:
        """
        Verify retreat can be made free with large negative modifier.

        Example: Float Stone sets retreat_cost_modifier to a large negative
        value to effectively make retreat free regardless of base cost.
        """
        instance = CardInstance(instance_id="uuid", definition_id="snorlax")
        instance.retreat_cost_modifier = -99  # Effectively free
        base_cost = 4

        assert instance.effective_retreat_cost(base_cost) == 0

    def test_effective_retreat_cost_minimum_is_zero(self) -> None:
        """
        Verify effective_retreat_cost never goes below 0.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.retreat_cost_modifier = -10
        base_cost = 2

        assert instance.effective_retreat_cost(base_cost) == 0

    def test_effective_retreat_cost_positive_modifier(self) -> None:
        """
        Verify effective_retreat_cost with positive modifier (increases cost).

        Some effects may increase retreat cost.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.retreat_cost_modifier = 2
        base_cost = 1

        assert instance.effective_retreat_cost(base_cost) == 3

    def test_damage_modifier_default(self) -> None:
        """
        Verify damage_modifier defaults to 0.

        The damage_modifier field exists for attack damage calculations
        in the game engine.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")

        assert instance.damage_modifier == 0

    def test_stat_modifiers_in_json_round_trip(self) -> None:
        """
        Verify stat modifiers survive JSON serialization.
        """
        instance = CardInstance(
            instance_id="uuid",
            definition_id="pikachu",
            hp_modifier=20,
            retreat_cost_modifier=-1,
            damage_modifier=10,
        )

        json_str = instance.model_dump_json()
        restored = CardInstance.model_validate_json(json_str)

        assert restored.hp_modifier == 20
        assert restored.retreat_cost_modifier == -1
        assert restored.damage_modifier == 10

    def test_effective_attack_cost_no_override(self) -> None:
        """
        Verify effective_attack_cost returns base cost when no override is set.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_cost = [EnergyType.LIGHTNING, EnergyType.COLORLESS]

        result = instance.effective_attack_cost(0, base_cost)

        assert result == base_cost

    def test_effective_attack_cost_with_override(self) -> None:
        """
        Verify effective_attack_cost returns override when set.

        Example: A control card effect completely changes the attack cost.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_cost = [EnergyType.LIGHTNING]
        override_cost = [EnergyType.PSYCHIC, EnergyType.PSYCHIC, EnergyType.COLORLESS]

        instance.attack_cost_overrides[0] = override_cost

        result = instance.effective_attack_cost(0, base_cost)

        assert result == override_cost
        assert result != base_cost

    def test_effective_attack_cost_different_attacks(self) -> None:
        """
        Verify attack cost overrides are per-attack (by index).

        One attack can be overridden while another uses base cost.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        attack_0_base = [EnergyType.LIGHTNING]
        attack_1_base = [EnergyType.LIGHTNING, EnergyType.LIGHTNING]

        # Only override attack 1
        instance.attack_cost_overrides[1] = [EnergyType.COLORLESS]

        # Attack 0 uses base cost
        assert instance.effective_attack_cost(0, attack_0_base) == attack_0_base
        # Attack 1 uses override
        assert instance.effective_attack_cost(1, attack_1_base) == [EnergyType.COLORLESS]

    def test_effective_attack_cost_add_colorless(self) -> None:
        """
        Verify adding colorless energy to an attack cost.

        Common control effect: "Your opponent's attacks cost 2 more colorless."
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_cost = [EnergyType.LIGHTNING]

        # Effect adds 2 colorless to the cost
        new_cost = base_cost + [EnergyType.COLORLESS, EnergyType.COLORLESS]
        instance.attack_cost_overrides[0] = new_cost

        result = instance.effective_attack_cost(0, base_cost)

        assert result == [EnergyType.LIGHTNING, EnergyType.COLORLESS, EnergyType.COLORLESS]

    def test_effective_attack_cost_make_free(self) -> None:
        """
        Verify making an attack cost nothing.

        Some effects allow attacks for free.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        base_cost = [EnergyType.LIGHTNING, EnergyType.LIGHTNING, EnergyType.COLORLESS]

        instance.attack_cost_overrides[0] = []  # Free!

        result = instance.effective_attack_cost(0, base_cost)

        assert result == []

    def test_attack_cost_overrides_in_json_round_trip(self) -> None:
        """
        Verify attack cost overrides survive JSON serialization.
        """
        instance = CardInstance(
            instance_id="uuid",
            definition_id="pikachu",
            attack_cost_overrides={
                0: [EnergyType.COLORLESS, EnergyType.COLORLESS],
                1: [EnergyType.PSYCHIC],
            },
        )

        json_str = instance.model_dump_json()
        restored = CardInstance.model_validate_json(json_str)

        assert restored.attack_cost_overrides[0] == [EnergyType.COLORLESS, EnergyType.COLORLESS]
        assert restored.attack_cost_overrides[1] == [EnergyType.PSYCHIC]


class TestCardInstanceStatusConditions:
    """Tests for status condition handling on CardInstance."""

    def test_add_poison(self) -> None:
        """
        Verify Poison can be added.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)

        assert instance.has_status(StatusCondition.POISONED)

    def test_add_burn(self) -> None:
        """
        Verify Burn can be added and stacks with Poison.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)
        instance.add_status(StatusCondition.BURNED)

        assert instance.has_status(StatusCondition.POISONED)
        assert instance.has_status(StatusCondition.BURNED)
        assert len(instance.status_conditions) == 2

    def test_asleep_overrides_paralyzed(self) -> None:
        """
        Verify Asleep overrides Paralyzed.

        Per GAME_RULES.md, Asleep/Paralyzed/Confused override each other.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.PARALYZED)
        assert instance.has_status(StatusCondition.PARALYZED)

        instance.add_status(StatusCondition.ASLEEP)
        assert instance.has_status(StatusCondition.ASLEEP)
        assert not instance.has_status(StatusCondition.PARALYZED)
        assert len(instance.status_conditions) == 1

    def test_paralyzed_overrides_asleep(self) -> None:
        """
        Verify Paralyzed overrides Asleep.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.ASLEEP)
        instance.add_status(StatusCondition.PARALYZED)

        assert instance.has_status(StatusCondition.PARALYZED)
        assert not instance.has_status(StatusCondition.ASLEEP)

    def test_confused_overrides_asleep(self) -> None:
        """
        Verify Confused overrides Asleep.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.ASLEEP)
        instance.add_status(StatusCondition.CONFUSED)

        assert instance.has_status(StatusCondition.CONFUSED)
        assert not instance.has_status(StatusCondition.ASLEEP)

    def test_poison_stacks_with_overriding_conditions(self) -> None:
        """
        Verify Poison stacks with Asleep/Paralyzed/Confused.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)
        instance.add_status(StatusCondition.ASLEEP)

        assert instance.has_status(StatusCondition.POISONED)
        assert instance.has_status(StatusCondition.ASLEEP)
        assert len(instance.status_conditions) == 2

    def test_remove_status(self) -> None:
        """
        Verify status conditions can be removed.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)
        instance.remove_status(StatusCondition.POISONED)

        assert not instance.has_status(StatusCondition.POISONED)

    def test_clear_all_status(self) -> None:
        """
        Verify all status conditions can be cleared (e.g., on retreat/evolve).
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)
        instance.add_status(StatusCondition.BURNED)
        instance.add_status(StatusCondition.ASLEEP)

        instance.clear_all_status()

        assert len(instance.status_conditions) == 0

    def test_duplicate_status_not_added(self) -> None:
        """
        Verify the same status isn't added twice.
        """
        instance = CardInstance(instance_id="uuid", definition_id="test")
        instance.add_status(StatusCondition.POISONED)
        instance.add_status(StatusCondition.POISONED)

        assert instance.status_conditions.count(StatusCondition.POISONED) == 1


class TestCardInstanceEvolution:
    """Tests for evolution-related CardInstance methods."""

    def test_can_evolve_after_one_turn(self) -> None:
        """
        Verify a Pokemon can evolve after being in play for a turn.
        """
        instance = CardInstance(
            instance_id="uuid",
            definition_id="pikachu",
            turn_played=1,
        )

        assert instance.can_evolve_this_turn(2) is True

    def test_cannot_evolve_same_turn_played(self) -> None:
        """
        Verify a Pokemon cannot evolve the same turn it was played.
        """
        instance = CardInstance(
            instance_id="uuid",
            definition_id="pikachu",
            turn_played=1,
        )

        assert instance.can_evolve_this_turn(1) is False

    def test_cannot_evolve_same_turn_as_evolution(self) -> None:
        """
        Verify a Pokemon cannot evolve again the same turn it evolved.
        """
        instance = CardInstance(
            instance_id="uuid",
            definition_id="charmeleon",
            turn_played=1,
            turn_evolved=3,
        )

        assert instance.can_evolve_this_turn(3) is False
        assert instance.can_evolve_this_turn(4) is True


class TestCardInstanceEnergy:
    """Tests for energy attachment on CardInstance."""

    def test_attach_energy(self) -> None:
        """
        Verify energy can be attached to a Pokemon.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        energy1 = CardInstance(instance_id="energy-001", definition_id="fire_energy")
        energy2 = CardInstance(instance_id="energy-002", definition_id="fire_energy")

        instance.attach_energy(energy1)
        instance.attach_energy(energy2)

        assert instance.energy_count() == 2
        assert any(e.instance_id == "energy-001" for e in instance.attached_energy)

    def test_detach_energy(self) -> None:
        """
        Verify energy can be detached from a Pokemon.

        detach_energy now returns the CardInstance or None.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        energy1 = CardInstance(instance_id="energy-001", definition_id="fire_energy")
        energy2 = CardInstance(instance_id="energy-002", definition_id="fire_energy")
        instance.attach_energy(energy1)
        instance.attach_energy(energy2)

        result = instance.detach_energy("energy-001")

        assert result is not None
        assert result.instance_id == "energy-001"
        assert instance.energy_count() == 1
        assert not any(e.instance_id == "energy-001" for e in instance.attached_energy)

    def test_detach_nonexistent_energy(self) -> None:
        """
        Verify detaching non-existent energy returns None.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")

        result = instance.detach_energy("nonexistent")

        assert result is None


class TestCardInstanceTurnState:
    """Tests for per-turn state on CardInstance."""

    def test_reset_turn_state(self) -> None:
        """
        Verify reset_turn_state resets ability usage counter to 0.

        This is called at the start of each turn to allow abilities
        to be used again.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        instance.ability_uses_this_turn = {0: 3, 1: 2}

        instance.reset_turn_state()

        assert instance.ability_uses_this_turn == {}

    def test_can_use_ability_once_per_turn(self) -> None:
        """
        Verify can_use_ability respects uses_per_turn = 1 (default).

        Standard Pokemon TCG abilities can be used once per turn.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        ability = Ability(name="Static", effect_id="static_paralysis")  # Default: uses_per_turn=1

        assert instance.can_use_ability(ability, ability_index=0)

        instance.increment_ability_uses(0)
        assert not instance.can_use_ability(ability, ability_index=0)

    def test_can_use_ability_multiple_per_turn(self) -> None:
        """
        Verify can_use_ability respects custom uses_per_turn limit.

        Some abilities can be used multiple times per turn.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        ability = Ability(name="Double Draw", effect_id="draw_cards", uses_per_turn=2)

        assert instance.can_use_ability(ability, ability_index=0)

        instance.increment_ability_uses(0)
        assert instance.can_use_ability(ability, ability_index=0)

        instance.increment_ability_uses(0)
        assert not instance.can_use_ability(ability, ability_index=0)

    def test_can_use_ability_unlimited(self) -> None:
        """
        Verify can_use_ability allows unlimited uses when uses_per_turn is None.

        Some abilities have no per-turn usage limit.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        ability = Ability(name="Energy Transfer", effect_id="move_energy", uses_per_turn=None)

        assert instance.can_use_ability(ability, ability_index=0)

        for _ in range(10):
            instance.increment_ability_uses(0)
        assert instance.can_use_ability(ability, ability_index=0)

        for _ in range(90):
            instance.increment_ability_uses(0)
        assert instance.can_use_ability(ability, ability_index=0)

    def test_can_use_ability_independent_tracking(self) -> None:
        """
        Verify that ability usage is tracked per-ability, not globally.

        A Pokemon with multiple abilities should track each one independently.
        Using ability 0 should not affect the usage count for ability 1.
        """
        instance = CardInstance(instance_id="uuid", definition_id="pikachu")
        ability_0 = Ability(name="Static", effect_id="static_paralysis", uses_per_turn=1)
        ability_1 = Ability(name="Double Draw", effect_id="draw_cards", uses_per_turn=2)

        # Both abilities should be usable initially
        assert instance.can_use_ability(ability_0, ability_index=0)
        assert instance.can_use_ability(ability_1, ability_index=1)

        # Use ability 0 once (hits its limit)
        instance.increment_ability_uses(0)
        assert not instance.can_use_ability(ability_0, ability_index=0)
        # Ability 1 should still be usable (independent tracking)
        assert instance.can_use_ability(ability_1, ability_index=1)

        # Use ability 1 twice (hits its limit)
        instance.increment_ability_uses(1)
        instance.increment_ability_uses(1)
        assert not instance.can_use_ability(ability_1, ability_index=1)

        # Verify counts
        assert instance.get_ability_uses(0) == 1
        assert instance.get_ability_uses(1) == 2
        assert instance.get_ability_uses(2) == 0  # Unused ability


class TestCardInstanceJsonRoundTrip:
    """Tests for JSON serialization of CardInstance."""

    def test_round_trip(self) -> None:
        """
        Verify CardInstance round-trips through JSON.

        attached_energy is now a list of CardInstance objects.
        """
        energy1 = CardInstance(instance_id="energy-001", definition_id="fire_energy")
        energy2 = CardInstance(instance_id="energy-002", definition_id="fire_energy")
        original = CardInstance(
            instance_id="uuid-12345",
            definition_id="pikachu_base_001",
            damage=30,
            attached_energy=[energy1, energy2],
            status_conditions=[StatusCondition.POISONED],
            turn_played=2,
        )

        json_str = original.model_dump_json()
        restored = CardInstance.model_validate_json(json_str)

        assert restored.instance_id == original.instance_id
        assert restored.damage == 30
        assert len(restored.attached_energy) == 2
        assert restored.attached_energy[0].instance_id == "energy-001"
        assert StatusCondition.POISONED in restored.status_conditions
