"""Tests for the RulesConfig and sub-configuration models.

These tests verify that:
1. Default values match Mantimon TCG house rules from GAME_RULES.md
2. Configuration can be customized via constructor or JSON
3. Nested configs work correctly
4. Helper methods function as expected
"""

import json

from app.core.config import (
    ActiveConfig,
    BenchConfig,
    DeckConfig,
    EnergyConfig,
    EvolutionConfig,
    FirstTurnConfig,
    PrizeConfig,
    RetreatConfig,
    RulesConfig,
    StatusConfig,
    TrainerConfig,
    WinConditionsConfig,
)
from app.core.enums import EnergyType, PokemonVariant


class TestDeckConfig:
    """Tests for DeckConfig."""

    def test_default_values(self) -> None:
        """
        Verify DeckConfig defaults match Mantimon TCG house rules.

        Per GAME_RULES.md: 40-card main deck, separate 20-card energy deck.
        """
        config = DeckConfig()

        assert config.min_size == 40
        assert config.max_size == 40
        assert config.exact_size_required is True
        assert config.max_copies_per_card == 4
        assert config.max_copies_basic_energy is None  # Unlimited
        assert config.min_basic_pokemon == 1
        assert config.energy_deck_enabled is True
        assert config.energy_deck_size == 20
        assert config.starting_hand_size == 7

    def test_custom_values(self) -> None:
        """
        Verify DeckConfig can be customized.

        This is important for free play mode where users can adjust rules.
        """
        config = DeckConfig(
            min_size=60,
            max_size=60,
            energy_deck_enabled=False,
        )

        assert config.min_size == 60
        assert config.max_size == 60
        assert config.energy_deck_enabled is False
        # Other values should still be defaults
        assert config.max_copies_per_card == 4

    def test_custom_starting_hand_size(self) -> None:
        """
        Verify starting_hand_size can be customized.

        Some game variants may use different starting hand sizes.
        """
        config = DeckConfig(starting_hand_size=5)
        assert config.starting_hand_size == 5

    def test_starting_hand_size_standard_tcg(self) -> None:
        """
        Verify starting_hand_size defaults to 7 (standard Pokemon TCG).

        This is the standard starting hand size across all Pokemon TCG eras.
        """
        config = DeckConfig()
        assert config.starting_hand_size == 7


class TestActiveConfig:
    """Tests for ActiveConfig."""

    def test_default_values(self) -> None:
        """
        Verify ActiveConfig defaults to standard single-battle (1 active).

        Standard Pokemon TCG has exactly one active Pokemon per player.
        """
        config = ActiveConfig()
        assert config.max_active == 1

    def test_double_battle_config(self) -> None:
        """
        Verify ActiveConfig can be configured for double battles.

        Double battle variants allow 2 active Pokemon per player.
        """
        config = ActiveConfig(max_active=2)
        assert config.max_active == 2

    def test_triple_battle_config(self) -> None:
        """
        Verify ActiveConfig supports exotic battle formats.

        While unusual, the config should support any number of active Pokemon.
        """
        config = ActiveConfig(max_active=3)
        assert config.max_active == 3


class TestBenchConfig:
    """Tests for BenchConfig."""

    def test_default_values(self) -> None:
        """
        Verify BenchConfig defaults to standard 5 Pokemon bench.
        """
        config = BenchConfig()
        assert config.max_size == 5

    def test_custom_bench_size(self) -> None:
        """
        Verify bench size can be customized for variant rules.
        """
        config = BenchConfig(max_size=8)
        assert config.max_size == 8


class TestEnergyConfig:
    """Tests for EnergyConfig."""

    def test_default_values(self) -> None:
        """
        Verify EnergyConfig defaults to Pokemon Pocket-style energy system.
        """
        config = EnergyConfig()

        assert config.attachments_per_turn == 1
        assert config.auto_flip_from_deck is True
        assert len(config.types_enabled) == 10
        assert EnergyType.FIRE in config.types_enabled
        assert EnergyType.COLORLESS in config.types_enabled

    def test_all_energy_types_enabled_by_default(self) -> None:
        """
        Verify all 10 energy types are enabled by default.
        """
        config = EnergyConfig()
        expected_types = set(EnergyType)
        actual_types = set(config.types_enabled)
        assert actual_types == expected_types

    def test_restrict_energy_types(self) -> None:
        """
        Verify energy types can be restricted for themed games.
        """
        config = EnergyConfig(
            types_enabled=[EnergyType.FIRE, EnergyType.WATER, EnergyType.COLORLESS]
        )
        assert len(config.types_enabled) == 3
        assert EnergyType.FIRE in config.types_enabled
        assert EnergyType.GRASS not in config.types_enabled


class TestPrizeConfig:
    """Tests for PrizeConfig."""

    def test_default_values(self) -> None:
        """
        Verify PrizeConfig defaults match Mantimon TCG house rules.

        Per GAME_RULES.md: 4 points to win, points instead of prize cards.
        Knockout points are based on variant (EX, V, etc.), not evolution stage.
        """
        config = PrizeConfig()

        assert config.count == 4
        assert config.per_knockout_normal == 1
        assert config.per_knockout_ex == 2
        assert config.per_knockout_gx == 2
        assert config.per_knockout_v == 2
        assert config.per_knockout_vmax == 3
        assert config.per_knockout_vstar == 3
        assert config.use_prize_cards is False

    def test_points_for_knockout_normal(self) -> None:
        """
        Verify points_for_knockout returns correct value for normal Pokemon.

        Normal Pokemon (non-variant) are worth 1 point regardless of evolution stage.
        """
        config = PrizeConfig()
        assert config.points_for_knockout(PokemonVariant.NORMAL) == 1

    def test_points_for_knockout_ex(self) -> None:
        """
        Verify points_for_knockout returns 2 for EX Pokemon.

        EX Pokemon are worth 2 knockout points per GAME_RULES.md.
        """
        config = PrizeConfig()
        assert config.points_for_knockout(PokemonVariant.EX) == 2

    def test_points_for_knockout_vmax(self) -> None:
        """
        Verify points_for_knockout returns 3 for VMAX Pokemon.

        VMAX are the highest-value Pokemon in the game.
        """
        config = PrizeConfig()
        assert config.points_for_knockout(PokemonVariant.VMAX) == 3

    def test_points_for_knockout_all_variants(self) -> None:
        """
        Verify points_for_knockout works for all Pokemon variants.

        Knockout points are determined by variant, not evolution stage.
        A Basic EX is worth the same as a Stage 2 EX.
        """
        config = PrizeConfig()

        assert config.points_for_knockout(PokemonVariant.NORMAL) == 1
        assert config.points_for_knockout(PokemonVariant.EX) == 2
        assert config.points_for_knockout(PokemonVariant.GX) == 2
        assert config.points_for_knockout(PokemonVariant.V) == 2
        assert config.points_for_knockout(PokemonVariant.VMAX) == 3
        assert config.points_for_knockout(PokemonVariant.VSTAR) == 3

    def test_custom_knockout_points(self) -> None:
        """
        Verify knockout point values can be customized.
        """
        config = PrizeConfig(per_knockout_normal=2, per_knockout_ex=3)
        assert config.points_for_knockout(PokemonVariant.NORMAL) == 2
        assert config.points_for_knockout(PokemonVariant.EX) == 3


class TestFirstTurnConfig:
    """Tests for FirstTurnConfig."""

    def test_default_values(self) -> None:
        """
        Verify FirstTurnConfig defaults match Mantimon TCG house rules.

        Per GAME_RULES.md:
        - First player CAN draw, attack, and play supporters on turn 1
        - First player CANNOT attach energy or evolve on turn 1
        """
        config = FirstTurnConfig()

        assert config.can_draw is True
        assert config.can_attack is True
        assert config.can_play_supporter is True
        assert config.can_attach_energy is False
        assert config.can_evolve is False

    def test_standard_pokemon_tcg_first_turn(self) -> None:
        """
        Verify configuration for standard Pokemon TCG first turn rules.

        In standard rules, first player cannot attack but can attach energy.
        """
        config = FirstTurnConfig(
            can_attack=False,
            can_play_supporter=False,
            can_attach_energy=True,
        )

        assert config.can_attack is False
        assert config.can_play_supporter is False
        assert config.can_attach_energy is True


class TestWinConditionsConfig:
    """Tests for WinConditionsConfig."""

    def test_default_values(self) -> None:
        """
        Verify WinConditionsConfig defaults enable all standard win conditions.
        """
        config = WinConditionsConfig()

        assert config.all_prizes_taken is True
        assert config.no_pokemon_in_play is True
        assert config.cannot_draw is True
        assert config.turn_limit_enabled is True
        assert config.turn_limit == 30
        assert config.turn_timer_enabled is False
        assert config.turn_timer_seconds == 90
        assert config.game_timer_enabled is False
        assert config.game_timer_minutes == 30

    def test_turn_limit_config(self) -> None:
        """
        Verify turn limit settings for AI/single-player matches.

        Turn limits are useful when real-time timers don't make sense,
        such as playing against AI or in puzzle mode.
        """
        config = WinConditionsConfig(
            turn_limit_enabled=True,
            turn_limit=20,
        )

        assert config.turn_limit_enabled is True
        assert config.turn_limit == 20

    def test_multiplayer_timer_config(self) -> None:
        """
        Verify timer settings for multiplayer mode.
        """
        config = WinConditionsConfig(
            turn_timer_enabled=True,
            turn_timer_seconds=120,
            game_timer_enabled=True,
            game_timer_minutes=45,
        )

        assert config.turn_timer_enabled is True
        assert config.turn_timer_seconds == 120
        assert config.game_timer_enabled is True
        assert config.game_timer_minutes == 45


class TestStatusConfig:
    """Tests for StatusConfig."""

    def test_default_values(self) -> None:
        """
        Verify StatusConfig defaults match GAME_RULES.md.

        Per documentation:
        - Poison: 10 damage between turns
        - Burn: 20 damage between turns, flip to remove
        - Confusion: 30 self-damage on tails
        """
        config = StatusConfig()

        assert config.poison_damage == 10
        assert config.burn_damage == 20
        assert config.burn_flip_to_remove is True
        assert config.sleep_flip_to_wake is True
        assert config.confusion_self_damage == 30


class TestTrainerConfig:
    """Tests for TrainerConfig."""

    def test_default_values(self) -> None:
        """
        Verify TrainerConfig defaults match standard rules.

        Per GAME_RULES.md:
        - Items: Unlimited per turn
        - Supporters: One per turn
        - Stadiums: One per turn
        - Tools: One per Pokemon
        - Same-name stadium replacement: Blocked (standard rules)
        """
        config = TrainerConfig()

        assert config.supporters_per_turn == 1
        assert config.stadiums_per_turn == 1
        assert config.items_per_turn is None  # Unlimited
        assert config.tools_per_pokemon == 1
        assert config.stadium_same_name_replace is False

    def test_stadium_same_name_replace_enabled(self) -> None:
        """
        Verify stadium_same_name_replace can be enabled for house rules.

        Some variants may allow replacing a stadium with the same stadium
        (e.g., to refresh its effects or reset counters).
        """
        config = TrainerConfig(stadium_same_name_replace=True)
        assert config.stadium_same_name_replace is True

    def test_stadium_same_name_replace_disabled_by_default(self) -> None:
        """
        Verify stadium_same_name_replace is disabled by default.

        In standard Pokemon TCG rules, you cannot play a stadium if a
        stadium with the same name is already in play.
        """
        config = TrainerConfig()
        assert config.stadium_same_name_replace is False


class TestEvolutionConfig:
    """Tests for EvolutionConfig."""

    def test_default_values(self) -> None:
        """
        Verify EvolutionConfig defaults to standard evolution rules.

        By default, Pokemon cannot evolve:
        - The same turn they were played
        - The same turn they already evolved
        - On the first turn of the game
        """
        config = EvolutionConfig()

        assert config.same_turn_as_played is False
        assert config.same_turn_as_evolution is False
        assert config.first_turn_of_game is False


class TestRetreatConfig:
    """Tests for RetreatConfig."""

    def test_default_values(self) -> None:
        """
        Verify RetreatConfig defaults to standard retreat rules.
        """
        config = RetreatConfig()

        assert config.retreats_per_turn == 1
        assert config.free_retreat_cost is False


class TestRulesConfig:
    """Tests for the master RulesConfig."""

    def test_default_instantiation(self) -> None:
        """
        Verify RulesConfig can be instantiated with all defaults.

        All nested configs should be created with their own defaults.
        """
        rules = RulesConfig()

        assert rules.deck.min_size == 40
        assert rules.active.max_active == 1
        assert rules.bench.max_size == 5
        assert rules.energy.attachments_per_turn == 1
        assert rules.prizes.count == 4
        assert rules.first_turn.can_attack is True
        assert rules.win_conditions.all_prizes_taken is True
        assert rules.status.poison_damage == 10
        assert rules.trainer.supporters_per_turn == 1
        assert rules.trainer.stadium_same_name_replace is False
        assert rules.evolution.same_turn_as_played is False
        assert rules.retreat.retreats_per_turn == 1

    def test_partial_override(self) -> None:
        """
        Verify RulesConfig allows partial overrides of nested configs.

        Only the specified values should change; others remain default.
        """
        rules = RulesConfig(
            deck=DeckConfig(min_size=60, max_size=60),
            prizes=PrizeConfig(count=6),
        )

        # Overridden values
        assert rules.deck.min_size == 60
        assert rules.deck.max_size == 60
        assert rules.prizes.count == 6

        # Default values still in place
        assert rules.deck.max_copies_per_card == 4
        assert rules.bench.max_size == 5
        assert rules.first_turn.can_attack is True

    def test_standard_pokemon_tcg_preset(self) -> None:
        """
        Verify standard_pokemon_tcg() returns configuration for official rules.

        This preset should override Mantimon defaults to match standard Pokemon TCG.
        """
        rules = RulesConfig.standard_pokemon_tcg()

        # Standard rules: 60-card deck, no energy deck
        assert rules.deck.min_size == 60
        assert rules.deck.max_size == 60
        assert rules.deck.energy_deck_enabled is False

        # Standard rules: 6 prizes with prize cards
        assert rules.prizes.count == 6
        assert rules.prizes.use_prize_cards is True

        # Standard rules: first player cannot attack or play supporter
        assert rules.first_turn.can_attack is False
        assert rules.first_turn.can_play_supporter is False
        assert rules.first_turn.can_attach_energy is True

    def test_json_round_trip(self) -> None:
        """
        Verify RulesConfig serializes and deserializes correctly.

        This is critical for:
        - Saving game configurations to database
        - Sending rules to clients
        - Loading preset rule configurations
        """
        original = RulesConfig(
            deck=DeckConfig(min_size=50),
            prizes=PrizeConfig(count=5, per_knockout_ex=3),
        )

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

        assert restored.deck.min_size == 50
        assert restored.prizes.count == 5
        assert restored.prizes.per_knockout_ex == 3
        # Defaults should be preserved
        assert restored.bench.max_size == 5

    def test_json_output_format(self) -> None:
        """
        Verify JSON output uses expected field names for compatibility.

        Field names in JSON should match the Python attribute names.
        """
        rules = RulesConfig()
        data = rules.model_dump()

        # Verify top-level keys
        assert "deck" in data
        assert "active" in data
        assert "bench" in data
        assert "energy" in data
        assert "prizes" in data
        assert "first_turn" in data
        assert "win_conditions" in data
        assert "status" in data
        assert "trainer" in data
        assert "evolution" in data
        assert "retreat" in data

        # Verify nested keys
        assert "min_size" in data["deck"]
        assert "max_size" in data["deck"]
        assert "max_active" in data["active"]
        assert "attachments_per_turn" in data["energy"]
        assert "stadium_same_name_replace" in data["trainer"]

    def test_nested_config_independence(self) -> None:
        """
        Verify nested configs don't share state between RulesConfig instances.

        Each RulesConfig should have its own copies of nested configs.
        """
        rules1 = RulesConfig()
        rules2 = RulesConfig()

        # Modify rules1's nested config
        rules1.deck.min_size = 100

        # rules2 should be unaffected
        assert rules2.deck.min_size == 40


class TestRulesConfigFromJson:
    """Tests for loading RulesConfig from JSON strings."""

    def test_load_minimal_json(self) -> None:
        """
        Verify RulesConfig can be loaded from minimal JSON.

        Missing fields should use defaults.
        """
        json_str = '{"prizes": {"count": 8}}'
        rules = RulesConfig.model_validate_json(json_str)

        assert rules.prizes.count == 8
        assert rules.deck.min_size == 40  # Default

    def test_load_full_json(self) -> None:
        """
        Verify RulesConfig can be loaded from comprehensive JSON.

        This tests the format documented in GAME_RULES.md.
        """
        config_dict = {
            "deck": {
                "min_size": 60,
                "max_size": 60,
                "max_copies_per_card": 4,
                "min_basic_pokemon": 1,
                "energy_deck_enabled": False,
            },
            "prizes": {
                "count": 6,
                "per_knockout_basic": 1,
                "per_knockout_ex": 2,
                "use_prize_cards": True,
            },
            "energy": {"attachments_per_turn": 1},
            "first_turn": {
                "can_draw": True,
                "can_attack": False,
                "can_play_supporter": False,
            },
            "win_conditions": {
                "all_prizes_taken": True,
                "no_pokemon_in_play": True,
                "cannot_draw": True,
            },
        }
        json_str = json.dumps(config_dict)
        rules = RulesConfig.model_validate_json(json_str)

        assert rules.deck.min_size == 60
        assert rules.deck.energy_deck_enabled is False
        assert rules.prizes.count == 6
        assert rules.prizes.use_prize_cards is True
        assert rules.first_turn.can_attack is False

    def test_empty_json_uses_all_defaults(self) -> None:
        """
        Verify empty JSON object creates config with all defaults.
        """
        rules = RulesConfig.model_validate_json("{}")

        assert rules.deck.min_size == 40
        assert rules.prizes.count == 4
        assert rules.first_turn.can_attack is True
