#!/usr/bin/env python
"""Engine validation script - reproducible walkthrough of game engine validation.

Usage:
    cd backend && uv run python references/engine_validation.py

This script demonstrates both successful operations and rejected illegal moves,
validating that the game engine correctly enforces rules. Uses seed=42 for
deterministic coin flips, ensuring reproducible results.

Press Enter to continue through each step, or 'q' to quit.

Validation Scenarios:
1. Setup - Create game with stacked decks
2. Illegal Moves - Verify rejected actions with clear error messages
3. Energy Mechanics - Attachment and attack cost validation
4. Weakness Demo - Additive +20 weakness calculation
5. Status Conditions - Paralysis and Poison effects
6. Knockout Flow - Points, forced actions, and state cleanup
7. Win Condition - Game ends at 4 points
"""

# =============================================================================
# PATH SETUP
# =============================================================================

import sys
from pathlib import Path

backend_dir = Path(__file__).resolve().parent.parent
if str(backend_dir) not in sys.path:
    sys.path.insert(0, str(backend_dir))

# =============================================================================
# IMPORTS
# =============================================================================

import asyncio

# Register effect handlers
import app.core.effects.handlers  # noqa: F401
from app.core import (
    CardType,
    EnergyType,
    GameEngine,
    GameState,
    PokemonStage,
    RulesConfig,
    TurnPhase,
    create_rng,
)
from app.core.enums import ModifierMode, StatusCondition
from app.core.models import (
    AttachEnergyAction,
    Attack,
    AttackAction,
    CardDefinition,
    CardInstance,
    EvolvePokemonAction,
    PassAction,
    RetreatAction,
    SelectActiveAction,
    WeaknessResistance,
)

# =============================================================================
# TERMINAL COLORS
# =============================================================================


class Colors:
    """ANSI color codes for terminal output."""

    HEADER = "\033[95m"
    BLUE = "\033[94m"
    CYAN = "\033[96m"
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    RED = "\033[91m"
    BOLD = "\033[1m"
    UNDERLINE = "\033[4m"
    END = "\033[0m"


def header(text: str) -> str:
    """Format text as a header."""
    return f"{Colors.BOLD}{Colors.HEADER}{text}{Colors.END}"


def success(text: str) -> str:
    """Format text as success (green)."""
    return f"{Colors.GREEN}{text}{Colors.END}"


def info(text: str) -> str:
    """Format text as info (cyan)."""
    return f"{Colors.CYAN}{text}{Colors.END}"


def warning(text: str) -> str:
    """Format text as warning (yellow)."""
    return f"{Colors.YELLOW}{text}{Colors.END}"


def error(text: str) -> str:
    """Format text as error (red)."""
    return f"{Colors.RED}{text}{Colors.END}"


def bold(text: str) -> str:
    """Format text as bold."""
    return f"{Colors.BOLD}{text}{Colors.END}"


# =============================================================================
# DISPLAY HELPERS
# =============================================================================


def print_divider(char: str = "=", width: int = 70):
    """Print a divider line."""
    print(char * width)


def print_section(title: str):
    """Print a section header."""
    print()
    print_divider()
    print(header(title))
    print_divider()


def print_subsection(title: str):
    """Print a subsection header."""
    print()
    print(f"{Colors.CYAN}--- {title} ---{Colors.END}")


def print_step(step: str, description: str):
    """Print a step in the validation."""
    print(f"\n{Colors.YELLOW}[{step}]{Colors.END} {description}")


def print_action(text: str):
    """Print an action being taken."""
    print(f"  {Colors.YELLOW}>>> {text}{Colors.END}")


def print_result(text: str):
    """Print the result of an action."""
    print(f"  {Colors.GREEN}    {text}{Colors.END}")


def print_expected_failure(text: str):
    """Print an expected failure (validation working correctly)."""
    print(f"  {Colors.RED}    REJECTED: {text}{Colors.END}")


def print_validation_pass(text: str):
    """Print a validation pass message."""
    print(f"  {Colors.GREEN}[PASS]{Colors.END} {text}")


def print_validation_fail(text: str):
    """Print a validation failure message."""
    print(f"  {Colors.RED}[FAIL]{Colors.END} {text}")


def show_game_state(game: GameState):
    """Display the current game state in a nice format."""
    print()
    print(f"  {bold('Turn:')} {game.turn_number}  |  {bold('Phase:')} {game.phase.value}  |  {bold('Current Player:')} {game.current_player_id}")

    for player_id in game.turn_order:
        player = game.players[player_id]
        is_current = player_id == game.current_player_id
        marker = " *" if is_current else ""

        print(f"\n  {bold(f'Player: {player_id}{marker}')}")
        print(f"    Score: {player.score} / {game.rules.prizes.count} to win")

        # Active Pokemon
        active = player.get_active_pokemon()
        if active:
            card_def = game.get_card_definition(active.definition_id)
            if card_def:
                max_hp = card_def.hp + active.hp_modifier
                current_hp = max_hp - active.damage
                status = ", ".join(s.value for s in active.status_conditions) or "none"
                energy = len(active.attached_energy)
                print(f"    {bold('Active:')} {card_def.name} ({current_hp}/{max_hp} HP, {energy} energy, status: {status})")
        else:
            print(f"    {bold('Active:')} {warning('None!')}")

        # Bench
        bench_names = []
        for card in player.bench.cards:
            card_def = game.get_card_definition(card.definition_id)
            if card_def:
                max_hp = card_def.hp + card.hp_modifier
                current_hp = max_hp - card.damage
                bench_names.append(f"{card_def.name} ({current_hp}/{max_hp})")
        bench_str = ", ".join(bench_names) if bench_names else "empty"
        print(f"    {bold('Bench:')} {bench_str}")

    print()


def show_pokemon_status(game: GameState, player_id: str, label: str = ""):
    """Show a single player's active Pokemon status."""
    player = game.players[player_id]
    active = player.get_active_pokemon()
    if active:
        card_def = game.get_card_definition(active.definition_id)
        if card_def:
            max_hp = card_def.hp + active.hp_modifier
            current_hp = max_hp - active.damage
            status = ", ".join(s.value for s in active.status_conditions) or "none"
            energy = len(active.attached_energy)
            prefix = f"{label}: " if label else ""
            print(f"    {prefix}{card_def.name} ({current_hp}/{max_hp} HP, {energy} energy, status: {status})")


def wait_for_continue() -> bool:
    """Wait for user to press Enter. Returns False if user wants to quit."""
    try:
        response = input(f"\n{info('[Press Enter to continue, or q to quit]')} ")
        return response.lower() != "q"
    except (KeyboardInterrupt, EOFError):
        return False


# =============================================================================
# CARD DEFINITIONS (Based on real Pokemon Pocket cards)
# =============================================================================


def create_card_definitions() -> dict[str, CardDefinition]:
    """Create all card definitions for validation.

    Uses real card stats from Pokemon Pocket where possible, but adds
    effect_id for attack effects since JSON cards only have effect_description.
    """
    cards = {}

    # === POKEMON CARDS ===

    # Pincurchin - from a1/112
    # Lightning, 70HP, Thunder Shock (30 dmg, coin flip paralysis)
    cards["pincurchin-001"] = CardDefinition(
        id="pincurchin-001",
        name="Pincurchin",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=70,
        pokemon_type=EnergyType.LIGHTNING,
        attacks=[
            Attack(
                name="Thunder Shock",
                cost=[EnergyType.LIGHTNING, EnergyType.LIGHTNING],
                damage=30,
                effect_id="attack_coin_status",
                effect_params={"status": "paralyzed"},
                effect_description="Flip a coin. If heads, opponent's Active is Paralyzed.",
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING, mode=ModifierMode.ADDITIVE, value=20),
        retreat_cost=1,
    )

    # Tentacruel - from a1/063
    # Water, 110HP, weak to Lightning (+20)
    cards["tentacruel-001"] = CardDefinition(
        id="tentacruel-001",
        name="Tentacruel",
        card_type=CardType.POKEMON,
        stage=PokemonStage.STAGE_1,
        evolves_from="Tentacool",
        hp=110,
        pokemon_type=EnergyType.WATER,
        attacks=[
            Attack(
                name="Poison Tentacles",
                cost=[EnergyType.WATER, EnergyType.COLORLESS],
                damage=50,
                effect_id="apply_status",
                effect_params={"status": "poisoned"},
                effect_description="Opponent's Active is now Poisoned.",
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.LIGHTNING, mode=ModifierMode.ADDITIVE, value=20),
        retreat_cost=2,
    )

    # Tentacool - basic for Tentacruel evolution (from a1/062)
    cards["tentacool-001"] = CardDefinition(
        id="tentacool-001",
        name="Tentacool",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=60,
        pokemon_type=EnergyType.WATER,
        attacks=[
            Attack(
                name="Gentle Slap",
                cost=[EnergyType.COLORLESS],
                damage=20,
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.LIGHTNING, mode=ModifierMode.ADDITIVE, value=20),
        retreat_cost=1,
    )

    # Grimer - from a1/174
    # Darkness, 70HP, Poison Gas (10 dmg, applies poison)
    cards["grimer-001"] = CardDefinition(
        id="grimer-001",
        name="Grimer",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=70,
        pokemon_type=EnergyType.DARKNESS,
        attacks=[
            Attack(
                name="Poison Gas",
                cost=[EnergyType.DARKNESS],
                damage=10,
                effect_id="apply_status",
                effect_params={"status": "poisoned"},
                effect_description="Opponent's Active is now Poisoned.",
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING, mode=ModifierMode.ADDITIVE, value=20),
        retreat_cost=3,
    )

    # Rattata - Basic Normal for evolution testing
    cards["rattata-001"] = CardDefinition(
        id="rattata-001",
        name="Rattata",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=40,
        pokemon_type=EnergyType.COLORLESS,
        attacks=[
            Attack(
                name="Bite",
                cost=[EnergyType.COLORLESS],
                damage=20,
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING),
        retreat_cost=0,
    )

    # Pikachu - for testing wrong evolution chain
    cards["pikachu-001"] = CardDefinition(
        id="pikachu-001",
        name="Pikachu",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        hp=60,
        pokemon_type=EnergyType.LIGHTNING,
        attacks=[
            Attack(
                name="Gnaw",
                cost=[],
                damage=10,
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING),
        retreat_cost=1,
    )

    # Raichu - Stage 1 for evolution testing (evolves from Pikachu, not Rattata)
    cards["raichu-001"] = CardDefinition(
        id="raichu-001",
        name="Raichu",
        card_type=CardType.POKEMON,
        stage=PokemonStage.STAGE_1,
        evolves_from="Pikachu",
        hp=100,
        pokemon_type=EnergyType.LIGHTNING,
        attacks=[
            Attack(
                name="Thunder Punch",
                cost=[EnergyType.LIGHTNING],
                damage=50,
            ),
        ],
        weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING),
        retreat_cost=1,
    )

    # === ENERGY CARDS ===

    for energy_type in [EnergyType.LIGHTNING, EnergyType.WATER, EnergyType.DARKNESS]:
        cards[f"{energy_type.value}-energy"] = CardDefinition(
            id=f"{energy_type.value}-energy",
            name=f"{energy_type.value.title()} Energy",
            card_type=CardType.ENERGY,
            energy_type=energy_type,
            energy_provides=[energy_type],
        )

    return cards


# =============================================================================
# DECK BUILDERS (Stacked for predictable scenarios)
# =============================================================================


def build_player1_deck(
    card_registry: dict[str, CardDefinition],
) -> tuple[list[CardInstance], list[CardInstance]]:
    """Build Player 1's deck - Pincurchin (Lightning) + filler.

    Stack order (top of deck first):
    - Pincurchin x3 (first will be in starting hand, ensuring active)
    - Rattata x1 (for wrong evolution test)
    - Pikachu x1 (for correct evolution test)
    - Raichu x1 (evolution card)
    - Filler to reach 40 cards
    """
    main_deck = []
    energy_deck = []

    # Stack top of deck for predictable hands
    # After shuffle, these will be in known positions due to seed=42
    # But we'll manipulate hand directly for cleaner test setup

    for i in range(3):
        main_deck.append(CardInstance(instance_id=f"p1-pincurchin-{i}", definition_id="pincurchin-001"))

    main_deck.append(CardInstance(instance_id="p1-rattata-0", definition_id="rattata-001"))
    main_deck.append(CardInstance(instance_id="p1-pikachu-0", definition_id="pikachu-001"))
    main_deck.append(CardInstance(instance_id="p1-raichu-0", definition_id="raichu-001"))

    # Pad to 40 cards
    idx = 0
    while len(main_deck) < 40:
        main_deck.append(CardInstance(instance_id=f"p1-pincurchin-extra-{idx}", definition_id="pincurchin-001"))
        idx += 1

    # Energy deck - 20 Lightning
    for i in range(20):
        energy_deck.append(CardInstance(instance_id=f"p1-lightning-{i}", definition_id="lightning-energy"))

    return main_deck, energy_deck


def build_player2_deck(
    card_registry: dict[str, CardDefinition],
) -> tuple[list[CardInstance], list[CardInstance]]:
    """Build Player 2's deck - Tentacool/Tentacruel (Water) + Grimer.

    Stack for predictable scenarios:
    - Tentacool x3 (for active and bench, weak to Lightning)
    - Tentacruel x2 (for evolution, also weak to Lightning)
    - Grimer x2 (for poison attack)
    - Filler
    """
    main_deck = []
    energy_deck = []

    for i in range(3):
        main_deck.append(CardInstance(instance_id=f"p2-tentacool-{i}", definition_id="tentacool-001"))

    for i in range(2):
        main_deck.append(CardInstance(instance_id=f"p2-tentacruel-{i}", definition_id="tentacruel-001"))

    for i in range(2):
        main_deck.append(CardInstance(instance_id=f"p2-grimer-{i}", definition_id="grimer-001"))

    # Pad to 40 cards
    idx = 0
    while len(main_deck) < 40:
        main_deck.append(CardInstance(instance_id=f"p2-tentacool-extra-{idx}", definition_id="tentacool-001"))
        idx += 1

    # Energy deck - mix of Water and Darkness
    for i in range(12):
        energy_deck.append(CardInstance(instance_id=f"p2-water-{i}", definition_id="water-energy"))
    for i in range(8):
        energy_deck.append(CardInstance(instance_id=f"p2-darkness-{i}", definition_id="darkness-energy"))

    return main_deck, energy_deck


# =============================================================================
# VALIDATION HELPERS
# =============================================================================


class ValidationTracker:
    """Track validation results for summary."""

    def __init__(self):
        self.passed = 0
        self.failed = 0
        self.results = []

    def check(self, condition: bool, description: str) -> bool:
        """Record a validation check result."""
        if condition:
            self.passed += 1
            self.results.append((True, description))
            print_validation_pass(description)
        else:
            self.failed += 1
            self.results.append((False, description))
            print_validation_fail(description)
        return condition

    def summary(self):
        """Print summary of all validations."""
        print_section("VALIDATION SUMMARY")
        total = self.passed + self.failed
        print(f"\n  Total checks: {total}")
        print(f"  {success(f'Passed: {self.passed}')}")
        if self.failed > 0:
            print(f"  {error(f'Failed: {self.failed}')}")
        else:
            print(f"  Failed: {self.failed}")

        if self.failed == 0:
            print(f"\n  {success('ALL VALIDATIONS PASSED!')}")
        else:
            print(f"\n  {error('SOME VALIDATIONS FAILED:')}")
            for passed, desc in self.results:
                if not passed:
                    print(f"    - {desc}")


# =============================================================================
# MAIN VALIDATION
# =============================================================================


async def run_validation():
    """Run the full engine validation suite."""
    print_divider("*")
    print(header("    MANTIMON TCG - ENGINE VALIDATION SCRIPT"))
    print_divider("*")

    print(f"\n{info('This script validates game engine behavior with reproducible scenarios.')}")
    print(f"{info('Using seed=42 for deterministic coin flips.')}")
    print(f"{info('Press Enter to advance through each step, or q to quit.')}")

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    tracker = ValidationTracker()

    # =========================================================================
    # PART 1: SETUP
    # =========================================================================

    print_section("PART 1: SETUP")

    print_step("1.1", "Creating card definitions")
    card_registry = create_card_definitions()
    print_result(f"Created {len(card_registry)} card definitions")

    print_step("1.2", "Creating rules and engine")
    rules = RulesConfig()
    rng = create_rng(seed=42)
    engine = GameEngine(rules=rules, rng=rng)
    print_result("Engine created with seed=42")

    print_step("1.3", "Building player decks")
    p1_deck, p1_energy = build_player1_deck(card_registry)
    p2_deck, p2_energy = build_player2_deck(card_registry)
    print_result(f"Player 1: {len(p1_deck)} cards main, {len(p1_energy)} cards energy")
    print_result(f"Player 2: {len(p2_deck)} cards main, {len(p2_energy)} cards energy")

    print_step("1.4", "Creating game")
    decks = {"player1": p1_deck, "player2": p2_deck}
    energy_decks = {"player1": p1_energy, "player2": p2_energy}

    creation = engine.create_game(
        player_ids=["player1", "player2"],
        decks=decks,
        energy_decks=energy_decks,
        card_registry=card_registry,
    )

    tracker.check(creation.success, "Game creation succeeded")
    game = creation.game
    print_result(f"Game ID: {game.game_id[:8]}...")
    print_result(f"First player: {game.current_player_id}")

    print_step("1.5", "Setting up controlled game state")
    # Manually set up the game state for predictable testing
    # This bypasses normal setup to get exact positions needed

    # Set player1 to go first
    game.current_player_id = "player1"

    # Clear hands and set up specific cards
    p1 = game.players["player1"]
    p2 = game.players["player2"]

    # Clear hands
    p1.hand.cards.clear()
    p2.hand.cards.clear()

    # Set up P1: Pincurchin active, Rattata + Pikachu on bench
    # (Rattata for wrong evolution test, Pikachu for correct evolution test)
    pincurchin = CardInstance(instance_id="p1-pincurchin-active", definition_id="pincurchin-001")
    p1.active.add(pincurchin)

    rattata = CardInstance(instance_id="p1-rattata-bench", definition_id="rattata-001")
    p1.bench.add(rattata)

    pikachu = CardInstance(instance_id="p1-pikachu-bench", definition_id="pikachu-001")
    p1.bench.add(pikachu)

    # Add Raichu to hand for evolution test
    raichu = CardInstance(instance_id="p1-raichu-hand", definition_id="raichu-001")
    p1.hand.add(raichu)

    # Set up P2: Tentacruel active (110 HP, survives weakness hit), Grimer + Tentacool on bench
    # Using Tentacruel instead of Tentacool for weakness demo (survives 50 damage)
    tentacruel_active = CardInstance(instance_id="p2-tentacruel-active", definition_id="tentacruel-001")
    p2.active.add(tentacruel_active)

    grimer = CardInstance(instance_id="p2-grimer-bench", definition_id="grimer-001")
    p2.bench.add(grimer)

    tentacool_bench = CardInstance(instance_id="p2-tentacool-bench", definition_id="tentacool-001")
    p2.bench.add(tentacool_bench)

    # Make sure energy zones have energy
    p1_energy_card = CardInstance(instance_id="p1-lightning-zone-0", definition_id="lightning-energy")
    p1.energy_zone.add(p1_energy_card)

    # Set to main phase for actions
    game.phase = TurnPhase.MAIN
    game.turn_number = 1

    print_result("Game state configured for testing")
    show_game_state(game)

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 2: ILLEGAL MOVES VALIDATION
    # =========================================================================

    print_section("PART 2: ILLEGAL MOVES VALIDATION")

    # 2.1: Attack without sufficient energy
    print_step("2.1", "Attack without sufficient energy")
    print_action("Pincurchin has 0 energy attached")
    print_action("Thunder Shock costs [Lightning, Lightning]")
    print_action("Moving to ATTACK phase and attempting attack...")

    # Must be in ATTACK phase to test energy validation
    game.phase = TurnPhase.ATTACK
    attack_action = AttackAction(attack_index=0)
    result = await engine.execute_action(game, "player1", attack_action)

    # Reset to MAIN phase for subsequent tests
    game.phase = TurnPhase.MAIN

    tracker.check(not result.success, "Attack without energy rejected")
    if not result.success:
        print_expected_failure(result.message)
        tracker.check(
            "energy" in result.message.lower() or "insufficient" in result.message.lower(),
            "Error message mentions energy requirement"
        )

    # 2.2: Wrong player's turn
    print_step("2.2", "Wrong player's turn")
    print_action("It's player1's turn")
    print_action("Player2 attempts to attach energy...")

    p2_energy = CardInstance(instance_id="p2-water-zone-0", definition_id="water-energy")
    p2.energy_zone.add(p2_energy)

    attach_action = AttachEnergyAction(
        energy_card_id="p2-water-zone-0",
        target_pokemon_id="p2-tentacool-active",
        from_energy_zone=True,
    )
    result = await engine.execute_action(game, "player2", attach_action)

    tracker.check(not result.success, "Action by wrong player rejected")
    if not result.success:
        print_expected_failure(result.message)
        tracker.check(
            "turn" in result.message.lower() or "player" in result.message.lower(),
            "Error message mentions turn/player"
        )

    # 2.3: Evolve on first turn
    print_step("2.3", "Evolve on first turn")
    print_action(f"Current turn number: {game.turn_number}")
    print_action("Attempting to evolve Pikachu into Raichu on turn 1...")

    # Mark Pikachu as played on turn 1 (just placed)
    pikachu.turn_played = 1

    evolve_action = EvolvePokemonAction(
        evolution_card_id="p1-raichu-hand",
        target_pokemon_id="p1-pikachu-bench",
    )
    result = await engine.execute_action(game, "player1", evolve_action)

    tracker.check(not result.success, "Evolution on first turn rejected")
    if not result.success:
        print_expected_failure(result.message)
        tracker.check(
            "first turn" in result.message.lower() or "turn 1" in result.message.lower() or "same turn" in result.message.lower(),
            "Error message mentions first turn restriction"
        )

    # 2.4: Wrong evolution chain
    print_step("2.4", "Wrong evolution chain")
    print_action("Raichu evolves from Pikachu, not Rattata")
    print_action("Attempting to evolve Rattata into Raichu...")

    # Advance turn so evolution is allowed, but chain is wrong
    game.turn_number = 2
    rattata.turn_played = 1  # Played on previous turn

    wrong_evolve = EvolvePokemonAction(
        evolution_card_id="p1-raichu-hand",
        target_pokemon_id="p1-rattata-bench",
    )
    result = await engine.execute_action(game, "player1", wrong_evolve)

    tracker.check(not result.success, "Wrong evolution chain rejected")
    if not result.success:
        print_expected_failure(result.message)
        tracker.check(
            "pikachu" in result.message.lower() or "evolution" in result.message.lower() or "evolves from" in result.message.lower(),
            "Error message mentions correct evolution chain"
        )

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 3: ENERGY MECHANICS
    # =========================================================================

    print_section("PART 3: ENERGY MECHANICS")

    print_step("3.1", "Attach energy from energy zone")
    print_action("Attaching Lightning energy to Pincurchin...")

    attach_action = AttachEnergyAction(
        energy_card_id="p1-lightning-zone-0",
        target_pokemon_id="p1-pincurchin-active",
        from_energy_zone=True,
    )
    result = await engine.execute_action(game, "player1", attach_action)

    tracker.check(result.success, "Energy attachment succeeded")
    active = p1.get_active_pokemon()
    tracker.check(len(active.attached_energy) == 1, "Pokemon has 1 energy attached")
    print_result(f"Pincurchin now has {len(active.attached_energy)} energy")

    print_step("3.2", "Second energy attachment (should fail)")
    print_action("Attempting second energy attachment this turn...")

    # Add another energy to zone
    p1_energy_2 = CardInstance(instance_id="p1-lightning-zone-1", definition_id="lightning-energy")
    p1.energy_zone.add(p1_energy_2)

    attach_action_2 = AttachEnergyAction(
        energy_card_id="p1-lightning-zone-1",
        target_pokemon_id="p1-pincurchin-active",
        from_energy_zone=True,
    )
    result = await engine.execute_action(game, "player1", attach_action_2)

    tracker.check(not result.success, "Second energy attachment rejected")
    if not result.success:
        print_expected_failure(result.message)

    print_step("3.3", "Attack still requires more energy")
    print_action("Pincurchin has 1 energy, Thunder Shock costs 2")
    print_action("Moving to ATTACK phase and attempting attack...")

    # Must be in ATTACK phase to test energy validation
    game.phase = TurnPhase.ATTACK
    attack_action = AttackAction(attack_index=0)
    result = await engine.execute_action(game, "player1", attack_action)

    # Reset to MAIN phase for subsequent tests
    game.phase = TurnPhase.MAIN

    tracker.check(not result.success, "Attack with insufficient energy rejected")
    if not result.success:
        print_expected_failure(result.message)
        tracker.check(
            "energy" in result.message.lower() or "insufficient" in result.message.lower(),
            "Error message mentions energy requirement"
        )

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 4: WEAKNESS DEMO
    # =========================================================================

    print_section("PART 4: WEAKNESS DEMONSTRATION")

    print_step("4.1", "Setting up for weakness test")
    print_action("Adding second energy to Pincurchin to enable attack")

    # Add energy directly to Pokemon for test (bypassing turn limit)
    energy_for_test = CardInstance(instance_id="p1-lightning-test", definition_id="lightning-energy")
    active.attach_energy(energy_for_test)

    print_result(f"Pincurchin now has {len(active.attached_energy)} energy")

    print_step("4.2", "Checking type matchup")
    print_action("Pincurchin (Lightning) vs Tentacruel (Water, weak to Lightning +20)")
    print_action("Tentacruel has 110 HP (survives the hit)")
    print_action("Thunder Shock base damage: 30")
    print_action("Expected damage: 30 + 20 = 50")

    # Advance to attack phase
    game.phase = TurnPhase.MAIN

    print_step("4.3", "Executing attack")
    # Move to attack phase first
    game.phase = TurnPhase.ATTACK
    attack_action = AttackAction(attack_index=0)
    result = await engine.execute_action(game, "player1", attack_action)

    tracker.check(result.success, "Attack executed successfully")

    # Check damage on opponent
    p2_active = p2.get_active_pokemon()

    print_result(f"Attack result: {result.message}")

    if p2_active:
        card_def = game.get_card_definition(p2_active.definition_id)
        print_result(f"Tentacruel took {p2_active.damage} damage (HP: {card_def.hp - p2_active.damage}/{card_def.hp})")
        tracker.check(p2_active.damage == 50, f"Weakness applied correctly: expected 50, got {p2_active.damage}")

        # Note: The attack also triggers coin flip for paralysis via attack_coin_status
        # With seed=42, we know the result - let's check
        if StatusCondition.PARALYZED in p2_active.status_conditions:
            print_result("Coin flip: HEADS - Tentacruel is paralyzed!")
        else:
            print_result("Coin flip: TAILS - No paralysis")
    else:
        print_result("Opponent's Pokemon was knocked out (unexpected for Tentacruel 110 HP)")
        tracker.check(False, "Tentacruel should survive 50 damage")

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 5: STATUS CONDITIONS
    # =========================================================================

    print_section("PART 5: STATUS CONDITIONS")

    # First, end player1's turn properly
    print_step("5.1", "Ending player1's turn")

    # End P1's turn
    end_result = engine.end_turn(game)
    print_result(f"Turn ended: {end_result.message}")
    print_result(f"Now player2's turn, turn number: {game.turn_number}")

    # Start P2's turn
    start_result = engine.start_turn(game)
    print_result(f"Turn started: {start_result.message}")

    # Check paralysis prevents attack
    print_step("5.2", "Paralysis prevents attack")

    # Check if Tentacruel is paralyzed
    p2_active = p2.get_active_pokemon()
    is_paralyzed = p2_active and StatusCondition.PARALYZED in p2_active.status_conditions

    if is_paralyzed:
        print_action("Tentacruel is paralyzed - attempting attack...")

        attack_action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player2", attack_action)

        tracker.check(not result.success, "Attack while paralyzed rejected")
        if not result.success:
            print_expected_failure(result.message)
    else:
        print_action("(Tentacool was not paralyzed from coin flip)")
        # Skip this check if no paralysis
        tracker.check(True, "Paralysis attack prevention (skipped - no paralysis)")

    print_step("5.3", "Paralysis prevents retreat")

    if is_paralyzed:
        print_action("Attempting to retreat while paralyzed...")

        retreat_action = RetreatAction(
            new_active_id="p2-grimer-bench",
            energy_to_discard=[],
        )
        result = await engine.execute_action(game, "player2", retreat_action)

        tracker.check(not result.success, "Retreat while paralyzed rejected")
        if not result.success:
            print_expected_failure(result.message)
    else:
        tracker.check(True, "Paralysis retreat prevention (skipped - no paralysis)")

    print_step("5.4", "Paralysis clears at end of turn")

    if is_paralyzed:
        print_action("Ending paralyzed Pokemon's turn...")

        # Pass turn since paralyzed can't do anything
        pass_action = PassAction()
        await engine.execute_action(game, "player2", pass_action)

        # End turn - paralysis should clear
        end_result = engine.end_turn(game)

        # Check paralysis is gone
        p2_active = p2.get_active_pokemon()
        paralysis_removed = StatusCondition.PARALYZED not in p2_active.status_conditions
        tracker.check(paralysis_removed, "Paralysis removed at end of turn")
        if paralysis_removed:
            print_result("Paralysis wore off!")
    else:
        tracker.check(True, "Paralysis removal (skipped - no paralysis)")

    print_step("5.5", "Poison damage at end of turn")
    print_action("Setting up Grimer to apply poison...")

    # Let's set up a clean scenario for poison
    # Switch P2's active to Grimer manually for simplicity

    # Make sure it's player2's turn and they can act
    if game.current_player_id != "player2":
        engine.start_turn(game)

    # Reset game state for clean poison test
    # Swap Grimer to active
    p2_active = p2.get_active_pokemon()
    if p2_active:
        p2.active.remove(p2_active.instance_id)
        p2.bench.add(p2_active)

    grimer_card = p2.bench.get("p2-grimer-bench")
    if grimer_card:
        p2.bench.remove("p2-grimer-bench")
        p2.active.add(grimer_card)

    # Add energy to Grimer
    darkness_energy = CardInstance(instance_id="p2-darkness-test", definition_id="darkness-energy")
    grimer_active = p2.get_active_pokemon()
    grimer_active.attach_energy(darkness_energy)

    # Reset turn state so we can attach energy
    p2.reset_turn_state()
    game.phase = TurnPhase.MAIN

    print_action("Grimer uses Poison Gas on Pincurchin...")

    # Move to attack phase
    game.phase = TurnPhase.ATTACK
    attack_action = AttackAction(attack_index=0)
    result = await engine.execute_action(game, "player2", attack_action)

    if result.success:
        print_result(f"Attack result: {result.message}")

        # Check if Pincurchin is poisoned
        p1_active = p1.get_active_pokemon()
        is_poisoned = StatusCondition.POISONED in p1_active.status_conditions
        tracker.check(is_poisoned, "Poison applied to target")

        if is_poisoned:
            damage_before = p1_active.damage
            print_result(f"Pincurchin is poisoned! Current damage: {damage_before}")

            # End P2's turn to trigger poison damage on P1
            end_result = engine.end_turn(game)
            print_result(f"Turn ended: {end_result.message}")

            # Note: Poison damage is applied at end of turn to the current player's Pokemon
            # But in the end_turn sequence, we advance to the next player
            # Actually, poison applies during between-turn effects
            # Let's check damage increased during the next player's end of turn

            # Start and complete P1's turn
            start_result = engine.start_turn(game)

            # Pass immediately to end turn
            pass_action = PassAction()
            await engine.execute_action(game, "player1", pass_action)

            # End turn - poison damage should apply
            end_result = engine.end_turn(game)

            damage_after = p1_active.damage
            poison_damage = damage_after - damage_before

            print_result(f"Poison dealt {poison_damage} damage")
            tracker.check(poison_damage == 10, f"Poison deals 10 damage (got {poison_damage})")
    else:
        print_expected_failure(f"Poison Gas attack failed: {result.message}")
        tracker.check(False, "Poison attack executed")

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 6: KNOCKOUT FLOW
    # =========================================================================

    print_section("PART 6: KNOCKOUT FLOW")

    print_step("6.1", "Setting up for knockout")

    # Reset game state for knockout test
    # Set P1's turn and give Pincurchin enough energy
    game.current_player_id = "player1"
    engine.start_turn(game)

    p1_active = p1.get_active_pokemon()
    # Make sure we have enough energy
    while len(p1_active.attached_energy) < 2:
        e = CardInstance(instance_id=f"p1-lightning-ko-{len(p1_active.attached_energy)}", definition_id="lightning-energy")
        p1_active.attach_energy(e)

    # Put Tentacool back as P2's active with low HP
    p2_active = p2.get_active_pokemon()
    if p2_active:
        p2.active.remove(p2_active.instance_id)
        p2.bench.add(p2_active)

    # Get a Tentacool for the KO test
    tentacool_ko = CardInstance(instance_id="p2-tentacool-ko", definition_id="tentacool-001")
    # Give it damage so next hit will KO (60 HP, Thunder Shock does 50 with weakness)
    tentacool_ko.damage = 20  # 40 HP remaining, 50 damage = KO
    p2.active.add(tentacool_ko)

    p2_active = p2.get_active_pokemon()
    card_def = game.get_card_definition(p2_active.definition_id)
    print_action(f"Tentacool has {card_def.hp - p2_active.damage}/{card_def.hp} HP")
    print_action("Thunder Shock will deal 50 damage (30 base + 20 weakness)")

    print_step("6.2", "Executing knockout attack")

    # Move to attack phase
    game.phase = TurnPhase.ATTACK
    attack_action = AttackAction(attack_index=0)
    result = await engine.execute_action(game, "player1", attack_action)

    tracker.check(result.success, "Knockout attack executed")
    print_result(f"Attack result: {result.message}")

    # Check score increased
    print_step("6.3", "Points awarded")
    print_result(f"Player 1 score: {p1.score}")
    tracker.check(p1.score > 0, "Points awarded for knockout")

    print_step("6.4", "Forced action for new active")

    # Check if forced action was set up
    forced = game.get_current_forced_action()
    if forced:
        tracker.check(forced.action_type == "select_active", "Forced action to select new active")
        print_result(f"Forced action: {forced.reason}")

        print_step("6.5", "Invalid action during forced action")
        print_action("Attempting to attack during forced select_active...")

        # Try to attack when forced action is pending
        attack_action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player2", attack_action)

        tracker.check(not result.success, "Non-forced action rejected")
        if not result.success:
            print_expected_failure(result.message)

        print_step("6.6", "Complete forced action")
        print_action("Player 2 selecting Grimer as new active...")

        # Find Grimer on bench
        grimer_id = None
        for card in p2.bench.cards:
            if card.definition_id == "grimer-001":
                grimer_id = card.instance_id
                break

        if grimer_id:
            select_action = SelectActiveAction(pokemon_id=grimer_id)
            result = await engine.execute_action(game, "player2", select_action)

            tracker.check(result.success, "New active selected successfully")
            if result.success:
                new_active = p2.get_active_pokemon()
                new_def = game.get_card_definition(new_active.definition_id)
                print_result(f"New active: {new_def.name}")
    else:
        # P2 might have no bench Pokemon left, which would be game over
        print_result("No forced action (opponent may have no benched Pokemon)")
        if result.win_result:
            print_result(f"Game ended: {result.win_result.reason}")
            tracker.check(True, "Win condition detected correctly")

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # PART 7: WIN CONDITION
    # =========================================================================

    print_section("PART 7: WIN CONDITION")

    # Check if game is already over
    if game.is_game_over():
        print_step("7.1", "Game already ended")
        print_result(f"Winner: {game.winner_id}")
        print_result(f"Reason: {game.end_reason}")
        tracker.check(True, "Win condition properly detected")
    else:
        print_step("7.1", "Simulate win by setting score to 4")
        print_action(f"Current scores: P1={p1.score}, P2={p2.score}")
        print_action(f"Points to win: {game.rules.prizes.count}")

        # Clear any forced actions for clean test
        game.forced_actions.clear()

        # Manually set score to trigger win condition
        print_action("Setting P1 score to 3, then executing a knockout for the win...")

        # Set P1 score to 3 (one away from winning)
        p1.score = 3
        print_result(f"P1 score set to: {p1.score}")

        # Make sure it's P1's turn with proper setup
        game.current_player_id = "player1"
        game.phase = TurnPhase.MAIN

        # Ensure P1 has an active Pokemon with energy
        p1_active = p1.get_active_pokemon()
        if not p1_active:
            p1_active = CardInstance(instance_id="p1-pincurchin-final", definition_id="pincurchin-001")
            p1.active.add(p1_active)

        while len(p1_active.attached_energy) < 2:
            e = CardInstance(instance_id=f"p1-lightning-final-{len(p1_active.attached_energy)}", definition_id="lightning-energy")
            p1_active.attach_energy(e)

        # Ensure P2 has a Tentacool (weak to Lightning) as active
        p2_active = p2.get_active_pokemon()
        if p2_active:
            # Remove current active and replace with Tentacool
            p2.active.remove(p2_active.instance_id)
            p2.bench.add(p2_active)

        # Create fresh Tentacool for predictable KO
        p2_active = CardInstance(instance_id="p2-tentacool-final", definition_id="tentacool-001")
        p2.active.add(p2_active)

        # Set damage so Thunder Shock (30 + 20 weakness = 50) will KO
        # Tentacool has 60 HP, so 20 damage = 40 HP left, 50 damage KOs
        p2_active.damage = 20
        print_action("P2 Tentacool (weak to Lightning) has 40/60 HP remaining")

        # Execute knockout attack
        print_step("7.2", "Execute winning knockout")
        game.phase = TurnPhase.ATTACK
        attack_action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", attack_action)

        print_result(f"Attack result: {result.message}")
        print_result(f"P1 final score: {p1.score}")

        win_triggered = False
        if result.win_result:
            print_result(f"Game ended: {result.win_result.reason}")
            tracker.check(True, "Win condition triggered correctly")
            win_triggered = True
        else:
            win_triggered = game.is_game_over()
            tracker.check(win_triggered, "Game ended after reaching 4 points")

        print_step("7.3", "Verify game over state")

        # Game should be over either via win_result or game.is_game_over()
        game_over = win_triggered or game.is_game_over()
        if game_over:
            tracker.check(True, "Game over detected")
            if game.winner_id:
                print_result(f"Winner: {game.winner_id}")
                print_result(f"End reason: {game.end_reason}")
            elif result.win_result:
                print_result(f"Winner: {result.win_result.winner_id}")
                print_result(f"End reason: {result.win_result.end_reason}")
        else:
            tracker.check(False, f"Game should be over but isn't (P1 score: {p1.score})")

    print_step("7.4", "Actions blocked after game over")

    # Use the game_over variable from Part 7 scope, or check again
    game_is_over = game.is_game_over() or (result and result.win_result is not None)
    if game_is_over:
        print_action("Attempting action after game over...")

        attack_action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", attack_action)

        tracker.check(not result.success, "Action after game over rejected")
        if not result.success:
            print_expected_failure(result.message)

    if not wait_for_continue():
        print("\nGoodbye!")
        return

    # =========================================================================
    # SUMMARY
    # =========================================================================

    tracker.summary()

    print(f"\n{info('Validation complete!')}")


# =============================================================================
# ENTRY POINT
# =============================================================================

if __name__ == "__main__":
    try:
        asyncio.run(run_validation())
    except KeyboardInterrupt:
        print("\n\nValidation interrupted. Goodbye!")
    except Exception as e:
        print(error(f"\nError: {e}"))
        import traceback
        traceback.print_exc()
        raise
