"""Tests for the action models.

These tests verify:
1. Each action type can be created and serialized
2. The discriminated union correctly identifies action types
3. parse_action correctly parses JSON into typed actions
4. VALID_PHASES_FOR_ACTION contains all action types
"""

import pytest
from pydantic import ValidationError

from app.core.models.actions import (
    VALID_PHASES_FOR_ACTION,
    Action,
    AttachEnergyAction,
    AttackAction,
    EvolvePokemonAction,
    PassAction,
    PlayPokemonAction,
    PlayTrainerAction,
    ResignAction,
    RetreatAction,
    SelectActiveAction,
    SelectPrizeAction,
    UseAbilityAction,
    parse_action,
)


class TestPlayPokemonAction:
    """Tests for PlayPokemonAction."""

    def test_basic_creation(self) -> None:
        """
        Verify PlayPokemonAction can be created with minimal fields.
        """
        action = PlayPokemonAction(card_instance_id="card-001")

        assert action.type == "play_pokemon"
        assert action.card_instance_id == "card-001"
        assert action.to_active is False

    def test_to_active_during_setup(self) -> None:
        """
        Verify to_active can be set for setup phase placement.
        """
        action = PlayPokemonAction(card_instance_id="card-001", to_active=True)

        assert action.to_active is True

    def test_json_round_trip(self) -> None:
        """
        Verify PlayPokemonAction serializes correctly.
        """
        original = PlayPokemonAction(card_instance_id="card-001", to_active=True)
        json_str = original.model_dump_json()
        restored = PlayPokemonAction.model_validate_json(json_str)

        assert restored.card_instance_id == original.card_instance_id
        assert restored.to_active == original.to_active


class TestEvolvePokemonAction:
    """Tests for EvolvePokemonAction."""

    def test_basic_creation(self) -> None:
        """
        Verify EvolvePokemonAction can be created.
        """
        action = EvolvePokemonAction(
            evolution_card_id="raichu-001",
            target_pokemon_id="pikachu-001",
        )

        assert action.type == "evolve"
        assert action.evolution_card_id == "raichu-001"
        assert action.target_pokemon_id == "pikachu-001"


class TestAttachEnergyAction:
    """Tests for AttachEnergyAction."""

    def test_basic_creation(self) -> None:
        """
        Verify AttachEnergyAction can be created.
        """
        action = AttachEnergyAction(
            energy_card_id="lightning-001",
            target_pokemon_id="pikachu-001",
        )

        assert action.type == "attach_energy"
        assert action.energy_card_id == "lightning-001"
        assert action.target_pokemon_id == "pikachu-001"
        assert action.from_energy_zone is False

    def test_from_energy_zone(self) -> None:
        """
        Verify energy can come from energy zone (Pokemon Pocket style).

        In Pokemon Pocket style gameplay, energy is flipped from the energy_deck
        to the energy_zone at turn start, then attached from there.
        """
        action = AttachEnergyAction(
            energy_card_id="lightning-001",
            target_pokemon_id="pikachu-001",
            from_energy_zone=True,
        )

        assert action.from_energy_zone is True


class TestPlayTrainerAction:
    """Tests for PlayTrainerAction."""

    def test_basic_creation(self) -> None:
        """
        Verify PlayTrainerAction can be created.
        """
        action = PlayTrainerAction(card_instance_id="potion-001")

        assert action.type == "play_trainer"
        assert action.card_instance_id == "potion-001"
        assert action.targets == []

    def test_with_targets(self) -> None:
        """
        Verify trainer cards can have targets.
        """
        action = PlayTrainerAction(
            card_instance_id="switch-001",
            targets=["pokemon-001", "pokemon-002"],
        )

        assert len(action.targets) == 2

    def test_with_additional_params(self) -> None:
        """
        Verify trainer cards can have additional parameters.
        """
        action = PlayTrainerAction(
            card_instance_id="pokeball-001",
            additional_params={"search_count": 3},
        )

        assert action.additional_params["search_count"] == 3


class TestUseAbilityAction:
    """Tests for UseAbilityAction."""

    def test_basic_creation(self) -> None:
        """
        Verify UseAbilityAction can be created.
        """
        action = UseAbilityAction(
            pokemon_id="pikachu-001",
            ability_index=0,
        )

        assert action.type == "use_ability"
        assert action.pokemon_id == "pikachu-001"
        assert action.ability_index == 0

    def test_with_targets(self) -> None:
        """
        Verify abilities can have targets.
        """
        action = UseAbilityAction(
            pokemon_id="alakazam-001",
            ability_index=0,
            targets=["charmander-001", "squirtle-001"],
        )

        assert len(action.targets) == 2


class TestAttackAction:
    """Tests for AttackAction."""

    def test_basic_creation(self) -> None:
        """
        Verify AttackAction can be created.
        """
        action = AttackAction(attack_index=0)

        assert action.type == "attack"
        assert action.attack_index == 0
        assert action.targets == []

    def test_with_targets(self) -> None:
        """
        Verify attacks can specify targets.
        """
        action = AttackAction(
            attack_index=1,
            targets=["benched-pokemon-001"],
        )

        assert action.targets == ["benched-pokemon-001"]

    def test_default_attack_index(self) -> None:
        """
        Verify attack_index defaults to 0.
        """
        action = AttackAction()
        assert action.attack_index == 0


class TestRetreatAction:
    """Tests for RetreatAction."""

    def test_basic_creation(self) -> None:
        """
        Verify RetreatAction can be created.
        """
        action = RetreatAction(new_active_id="bulbasaur-001")

        assert action.type == "retreat"
        assert action.new_active_id == "bulbasaur-001"
        assert action.energy_to_discard == []

    def test_with_energy_discard(self) -> None:
        """
        Verify retreat cost energy can be specified.
        """
        action = RetreatAction(
            new_active_id="bulbasaur-001",
            energy_to_discard=["energy-001", "energy-002"],
        )

        assert len(action.energy_to_discard) == 2


class TestPassAction:
    """Tests for PassAction."""

    def test_basic_creation(self) -> None:
        """
        Verify PassAction can be created.
        """
        action = PassAction()

        assert action.type == "pass"


class TestSelectPrizeAction:
    """Tests for SelectPrizeAction."""

    def test_basic_creation(self) -> None:
        """
        Verify SelectPrizeAction can be created.
        """
        action = SelectPrizeAction(prize_index=2)

        assert action.type == "select_prize"
        assert action.prize_index == 2


class TestSelectActiveAction:
    """Tests for SelectActiveAction."""

    def test_basic_creation(self) -> None:
        """
        Verify SelectActiveAction can be created.
        """
        action = SelectActiveAction(pokemon_id="bulbasaur-001")

        assert action.type == "select_active"
        assert action.pokemon_id == "bulbasaur-001"


class TestResignAction:
    """Tests for ResignAction."""

    def test_basic_creation(self) -> None:
        """
        Verify ResignAction can be created.
        """
        action = ResignAction()

        assert action.type == "resign"


class TestActionUnion:
    """Tests for the Action discriminated union."""

    def test_parse_play_pokemon(self) -> None:
        """
        Verify parse_action correctly identifies PlayPokemonAction.
        """
        data = {"type": "play_pokemon", "card_instance_id": "card-001"}
        action = parse_action(data)

        assert isinstance(action, PlayPokemonAction)
        assert action.card_instance_id == "card-001"

    def test_parse_evolve(self) -> None:
        """
        Verify parse_action correctly identifies EvolvePokemonAction.
        """
        data = {
            "type": "evolve",
            "evolution_card_id": "raichu-001",
            "target_pokemon_id": "pikachu-001",
        }
        action = parse_action(data)

        assert isinstance(action, EvolvePokemonAction)

    def test_parse_attach_energy(self) -> None:
        """
        Verify parse_action correctly identifies AttachEnergyAction.
        """
        data = {
            "type": "attach_energy",
            "energy_card_id": "lightning-001",
            "target_pokemon_id": "pikachu-001",
        }
        action = parse_action(data)

        assert isinstance(action, AttachEnergyAction)

    def test_parse_play_trainer(self) -> None:
        """
        Verify parse_action correctly identifies PlayTrainerAction.
        """
        data = {"type": "play_trainer", "card_instance_id": "potion-001"}
        action = parse_action(data)

        assert isinstance(action, PlayTrainerAction)

    def test_parse_use_ability(self) -> None:
        """
        Verify parse_action correctly identifies UseAbilityAction.
        """
        data = {"type": "use_ability", "pokemon_id": "alakazam-001"}
        action = parse_action(data)

        assert isinstance(action, UseAbilityAction)

    def test_parse_attack(self) -> None:
        """
        Verify parse_action correctly identifies AttackAction.
        """
        data = {"type": "attack", "attack_index": 1}
        action = parse_action(data)

        assert isinstance(action, AttackAction)
        assert action.attack_index == 1

    def test_parse_retreat(self) -> None:
        """
        Verify parse_action correctly identifies RetreatAction.
        """
        data = {"type": "retreat", "new_active_id": "bulbasaur-001"}
        action = parse_action(data)

        assert isinstance(action, RetreatAction)

    def test_parse_pass(self) -> None:
        """
        Verify parse_action correctly identifies PassAction.
        """
        data = {"type": "pass"}
        action = parse_action(data)

        assert isinstance(action, PassAction)

    def test_parse_select_prize(self) -> None:
        """
        Verify parse_action correctly identifies SelectPrizeAction.
        """
        data = {"type": "select_prize", "prize_index": 3}
        action = parse_action(data)

        assert isinstance(action, SelectPrizeAction)

    def test_parse_select_active(self) -> None:
        """
        Verify parse_action correctly identifies SelectActiveAction.
        """
        data = {"type": "select_active", "pokemon_id": "squirtle-001"}
        action = parse_action(data)

        assert isinstance(action, SelectActiveAction)

    def test_parse_resign(self) -> None:
        """
        Verify parse_action correctly identifies ResignAction.
        """
        data = {"type": "resign"}
        action = parse_action(data)

        assert isinstance(action, ResignAction)

    def test_parse_unknown_type_raises(self) -> None:
        """
        Verify parse_action raises an error for unknown action types.
        """
        data = {"type": "unknown_action"}

        with pytest.raises(ValidationError):
            parse_action(data)

    def test_parse_missing_required_field_raises(self) -> None:
        """
        Verify parse_action raises an error when required fields are missing.
        """
        data = {"type": "play_pokemon"}  # Missing card_instance_id

        with pytest.raises(ValidationError):
            parse_action(data)


class TestActionJsonSerialization:
    """Tests for JSON serialization of all action types."""

    @pytest.mark.parametrize(
        "action",
        [
            PlayPokemonAction(card_instance_id="card-001"),
            EvolvePokemonAction(evolution_card_id="evo-001", target_pokemon_id="base-001"),
            AttachEnergyAction(energy_card_id="energy-001", target_pokemon_id="pokemon-001"),
            PlayTrainerAction(card_instance_id="trainer-001"),
            UseAbilityAction(pokemon_id="pokemon-001"),
            AttackAction(attack_index=0),
            RetreatAction(new_active_id="bench-001"),
            PassAction(),
            SelectPrizeAction(prize_index=0),
            SelectActiveAction(pokemon_id="bench-001"),
            ResignAction(),
        ],
    )
    def test_json_round_trip(self, action: Action) -> None:
        """
        Verify all action types round-trip through JSON correctly.
        """
        # Test JSON serialization via dict (what websocket messages use)
        data = action.model_dump()
        restored = parse_action(data)

        assert type(restored) is type(action)
        assert restored.type == action.type


class TestValidPhasesForAction:
    """Tests for the VALID_PHASES_FOR_ACTION mapping."""

    def test_all_action_types_have_valid_phases(self) -> None:
        """
        Verify every action type has a valid phases entry.
        """
        action_types = [
            "play_pokemon",
            "evolve",
            "attach_energy",
            "play_trainer",
            "use_ability",
            "attack",
            "retreat",
            "pass",
            "select_prize",
            "select_active",
            "resign",
        ]

        for action_type in action_types:
            assert action_type in VALID_PHASES_FOR_ACTION, f"Missing: {action_type}"
            assert len(VALID_PHASES_FOR_ACTION[action_type]) > 0

    def test_attack_only_in_attack_phase(self) -> None:
        """
        Verify attack is only valid during the attack phase.
        """
        assert VALID_PHASES_FOR_ACTION["attack"] == ["attack"]

    def test_resign_valid_any_phase(self) -> None:
        """
        Verify resign can be done at any time.
        """
        phases = VALID_PHASES_FOR_ACTION["resign"]
        assert "setup" in phases
        assert "draw" in phases
        assert "main" in phases
        assert "attack" in phases
        assert "end" in phases

    def test_pass_valid_in_main_and_attack(self) -> None:
        """
        Verify pass is valid in main phase (to skip to attack) and
        attack phase (to end turn without attacking).
        """
        phases = VALID_PHASES_FOR_ACTION["pass"]
        assert "main" in phases
        assert "attack" in phases
