"""Tests for the enumeration types in the game engine.

These tests verify that all enums:
1. Have expected values that won't accidentally change
2. Serialize correctly to JSON (as strings)
3. Can be used in Pydantic models
4. Support membership checks and iteration
"""

import json

import pytest
from pydantic import BaseModel

from app.core.enums import (
    ActionType,
    CardType,
    EnergyType,
    GameEndReason,
    PokemonStage,
    PokemonVariant,
    StatusCondition,
    TrainerType,
    TurnPhase,
)


class TestCardType:
    """Tests for the CardType enum."""

    def test_card_type_values(self) -> None:
        """
        Verify CardType has exactly three types with expected string values.

        The three card types are fundamental to the game and should never change.
        """
        assert CardType.POKEMON == "pokemon"
        assert CardType.TRAINER == "trainer"
        assert CardType.ENERGY == "energy"
        assert len(CardType) == 3

    def test_card_type_json_serialization(self) -> None:
        """
        Verify CardType serializes to a plain string in JSON.

        This is important for WebSocket messages and database storage.
        """
        assert json.dumps(CardType.POKEMON) == '"pokemon"'

    def test_card_type_in_pydantic_model(self) -> None:
        """
        Verify CardType works correctly in Pydantic models.

        Pydantic should accept both enum values and string values.
        """

        class CardModel(BaseModel):
            card_type: CardType

        # Accept enum value
        model = CardModel(card_type=CardType.POKEMON)
        assert model.card_type == CardType.POKEMON

        # Accept string value
        model = CardModel(card_type="trainer")  # type: ignore[arg-type]
        assert model.card_type == CardType.TRAINER

        # JSON round-trip
        json_str = model.model_dump_json()
        restored = CardModel.model_validate_json(json_str)
        assert restored.card_type == CardType.TRAINER


class TestPokemonStage:
    """Tests for the PokemonStage enum."""

    def test_pokemon_stage_values(self) -> None:
        """
        Verify PokemonStage has the three evolution stages only.

        Evolution stages determine how a Pokemon can be played:
        - BASIC: Can be played directly from hand
        - STAGE_1: Must evolve from a Basic
        - STAGE_2: Must evolve from a Stage 1

        Note: Variants (EX, V, GX, etc.) are now in PokemonVariant enum.
        """
        assert PokemonStage.BASIC == "basic"
        assert PokemonStage.STAGE_1 == "stage_1"
        assert PokemonStage.STAGE_2 == "stage_2"
        assert len(PokemonStage) == 3

    def test_pokemon_stage_membership(self) -> None:
        """
        Verify membership checks work for PokemonStage.

        This is used to validate card definitions.
        """
        assert "basic" in [s.value for s in PokemonStage]
        assert "invalid" not in [s.value for s in PokemonStage]
        # Variants are NOT in PokemonStage anymore
        assert "ex" not in [s.value for s in PokemonStage]
        assert "v" not in [s.value for s in PokemonStage]


class TestPokemonVariant:
    """Tests for the PokemonVariant enum."""

    def test_pokemon_variant_values(self) -> None:
        """
        Verify PokemonVariant has all expected variant classifications.

        Variants affect knockout points but are orthogonal to evolution stage.
        A card can be "Basic EX" or "Stage 2 GX".
        """
        assert PokemonVariant.NORMAL == "normal"
        assert PokemonVariant.EX == "ex"
        assert PokemonVariant.GX == "gx"
        assert PokemonVariant.V == "v"
        assert PokemonVariant.VMAX == "vmax"
        assert PokemonVariant.VSTAR == "vstar"
        assert len(PokemonVariant) == 6

    def test_pokemon_variant_membership(self) -> None:
        """
        Verify membership checks work for PokemonVariant.

        This is used to validate card definitions and calculate knockout points.
        """
        assert "normal" in [v.value for v in PokemonVariant]
        assert "ex" in [v.value for v in PokemonVariant]
        assert "invalid" not in [v.value for v in PokemonVariant]

    def test_variant_knockout_point_categories(self) -> None:
        """
        Document which variants are worth more knockout points.

        - NORMAL: 1 point (standard Pokemon)
        - EX, GX, V: 2 points
        - VMAX, VSTAR: 3 points
        """
        one_point_variants = {PokemonVariant.NORMAL}
        two_point_variants = {
            PokemonVariant.EX,
            PokemonVariant.GX,
            PokemonVariant.V,
        }
        three_point_variants = {PokemonVariant.VMAX, PokemonVariant.VSTAR}

        # Verify all variants are accounted for
        all_variants = set(PokemonVariant)
        assert one_point_variants | two_point_variants | three_point_variants == all_variants

    def test_variants_requiring_v_evolution(self) -> None:
        """
        Document which variants must evolve from a V Pokemon.

        VMAX and VSTAR are special - they always evolve from a V variant,
        regardless of the base Pokemon's evolution stage.
        """
        v_evolution_variants = {PokemonVariant.VMAX, PokemonVariant.VSTAR}
        assert PokemonVariant.VMAX in v_evolution_variants
        assert PokemonVariant.VSTAR in v_evolution_variants


class TestEnergyType:
    """Tests for the EnergyType enum."""

    def test_energy_type_values(self) -> None:
        """
        Verify EnergyType has all 10 modern Pokemon TCG energy types.

        The types should match the documented 10 types from GAME_RULES.md.
        """
        expected_types = {
            "colorless",
            "darkness",
            "dragon",
            "fighting",
            "fire",
            "grass",
            "lightning",
            "metal",
            "psychic",
            "water",
        }
        actual_types = {e.value for e in EnergyType}
        assert actual_types == expected_types
        assert len(EnergyType) == 10

    def test_colorless_is_special(self) -> None:
        """
        Verify COLORLESS energy type exists.

        Colorless has special meaning - any energy can satisfy colorless requirements.
        This test documents that assumption.
        """
        assert EnergyType.COLORLESS == "colorless"
        assert EnergyType.COLORLESS in EnergyType

    def test_energy_type_iteration(self) -> None:
        """
        Verify EnergyType can be iterated for building default configs.

        The engine uses list(EnergyType) to get all available types.
        """
        all_types = list(EnergyType)
        assert len(all_types) == 10
        assert all(isinstance(t, EnergyType) for t in all_types)


class TestTrainerType:
    """Tests for the TrainerType enum."""

    def test_trainer_type_values(self) -> None:
        """
        Verify TrainerType has all four trainer subtypes.

        These match the modern Pokemon TCG trainer card structure.
        """
        assert TrainerType.ITEM == "item"
        assert TrainerType.SUPPORTER == "supporter"
        assert TrainerType.STADIUM == "stadium"
        assert TrainerType.TOOL == "tool"
        assert len(TrainerType) == 4


class TestTurnPhase:
    """Tests for the TurnPhase enum."""

    def test_turn_phase_values(self) -> None:
        """
        Verify TurnPhase has all five phases in the turn structure.

        The phases follow the order: SETUP -> DRAW -> MAIN -> ATTACK -> END.
        """
        assert TurnPhase.SETUP == "setup"
        assert TurnPhase.DRAW == "draw"
        assert TurnPhase.MAIN == "main"
        assert TurnPhase.ATTACK == "attack"
        assert TurnPhase.END == "end"
        assert len(TurnPhase) == 5

    def test_turn_phase_order(self) -> None:
        """
        Verify turn phases can be compared for ordering.

        While we don't rely on enum ordering for logic, this documents
        the expected sequence.
        """
        phases = list(TurnPhase)
        assert phases[0] == TurnPhase.SETUP
        assert phases[-1] == TurnPhase.END


class TestStatusCondition:
    """Tests for the StatusCondition enum."""

    def test_status_condition_values(self) -> None:
        """
        Verify StatusCondition has all five status conditions.

        These match the status conditions documented in GAME_RULES.md.
        """
        assert StatusCondition.POISONED == "poisoned"
        assert StatusCondition.BURNED == "burned"
        assert StatusCondition.ASLEEP == "asleep"
        assert StatusCondition.PARALYZED == "paralyzed"
        assert StatusCondition.CONFUSED == "confused"
        assert len(StatusCondition) == 5

    def test_stacking_conditions(self) -> None:
        """
        Document which conditions can stack with others.

        POISONED and BURNED can stack with any condition.
        ASLEEP, PARALYZED, and CONFUSED override each other.
        """
        stacking_conditions = {StatusCondition.POISONED, StatusCondition.BURNED}
        overriding_conditions = {
            StatusCondition.ASLEEP,
            StatusCondition.PARALYZED,
            StatusCondition.CONFUSED,
        }

        # Verify all conditions are accounted for
        all_conditions = set(StatusCondition)
        assert stacking_conditions | overriding_conditions == all_conditions


class TestActionType:
    """Tests for the ActionType enum."""

    def test_action_type_values(self) -> None:
        """
        Verify ActionType has all expected player actions.

        These are all the actions a player can take during their turn.
        """
        expected_actions = {
            "play_pokemon",
            "evolve",
            "attach_energy",
            "play_trainer",
            "use_ability",
            "attack",
            "retreat",
            "pass",
        }
        actual_actions = {a.value for a in ActionType}
        assert actual_actions == expected_actions
        assert len(ActionType) == 8


class TestGameEndReason:
    """Tests for the GameEndReason enum."""

    def test_game_end_reason_values(self) -> None:
        """
        Verify GameEndReason has all expected game ending conditions.

        These match the win conditions documented in GAME_RULES.md plus
        additional end conditions like resignation and timeout.
        """
        assert GameEndReason.PRIZES_TAKEN == "prizes_taken"
        assert GameEndReason.NO_POKEMON == "no_pokemon"
        assert GameEndReason.DECK_EMPTY == "deck_empty"
        assert GameEndReason.RESIGNATION == "resignation"
        assert GameEndReason.TIMEOUT == "timeout"
        assert GameEndReason.TURN_LIMIT == "turn_limit"
        assert GameEndReason.DRAW == "draw"
        assert len(GameEndReason) == 7


class TestEnumJsonRoundTrip:
    """Tests for JSON serialization/deserialization of all enums."""

    @pytest.mark.parametrize(
        "enum_class,sample_value",
        [
            (CardType, CardType.POKEMON),
            (PokemonStage, PokemonStage.BASIC),
            (PokemonVariant, PokemonVariant.EX),
            (EnergyType, EnergyType.FIRE),
            (TrainerType, TrainerType.SUPPORTER),
            (TurnPhase, TurnPhase.MAIN),
            (StatusCondition, StatusCondition.POISONED),
            (ActionType, ActionType.ATTACK),
            (GameEndReason, GameEndReason.PRIZES_TAKEN),
        ],
    )
    def test_json_round_trip(self, enum_class: type, sample_value: str) -> None:
        """
        Verify all enum types round-trip through JSON correctly.

        This is critical for WebSocket communication and state persistence.
        Each enum should serialize to its string value and deserialize back.
        """

        class TestModel(BaseModel):
            value: enum_class  # type: ignore[valid-type]

        model = TestModel(value=sample_value)
        json_str = model.model_dump_json()
        restored = TestModel.model_validate_json(json_str)

        assert restored.value == sample_value
        assert isinstance(restored.value, enum_class)
