"""Win condition checking for the Mantimon TCG game engine.

This module implements config-driven win condition checking. The game supports
multiple win conditions that can be independently enabled or disabled via
the RulesConfig.win_conditions settings.

Win Conditions (when enabled):
    - all_prizes_taken: A player scores enough points (or takes all prize cards)
    - no_pokemon_in_play: A player's opponent has no Pokemon in play
    - cannot_draw: A player cannot draw at the start of their turn

The win condition checker is typically called:
    - After resolving an attack (Pokemon knockouts may trigger win)
    - At the start of a turn's draw phase (deck empty check)
    - After any effect that removes Pokemon from play

Usage:
    from app.core.win_conditions import check_win_conditions, WinResult

    # Check if anyone has won
    result = check_win_conditions(game_state)
    if result is not None:
        print(f"Player {result.winner_id} wins: {result.reason}")
        game_state.set_winner(result.winner_id, result.end_reason)

    # Check specific conditions
    from app.core.win_conditions import (
        check_prizes_taken,
        check_no_pokemon_in_play,
        check_cannot_draw,
    )

    prizes_result = check_prizes_taken(game_state)
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel

from app.core.enums import GameEndReason

if TYPE_CHECKING:
    from app.core.models.game_state import GameState


class WinResult(BaseModel):
    """Result indicating a player has won the game.

    Attributes:
        winner_id: The player ID of the winner.
        loser_id: The player ID of the loser.
        end_reason: The GameEndReason enum value for why the game ended.
        reason: Human-readable explanation of the win condition.
    """

    winner_id: str
    loser_id: str
    end_reason: GameEndReason
    reason: str


def check_win_conditions(game: GameState) -> WinResult | None:
    """Check all enabled win conditions and return a result if any are met.

    This is the main entry point for win condition checking. It checks each
    enabled condition in priority order and returns immediately if any
    condition is met.

    Check order:
        1. Prizes/Points taken (most common win)
        2. No Pokemon in play (opponent lost all Pokemon)
        3. Cannot draw (deck empty at turn start)

    The turn limit condition is NOT checked here - it should be checked by
    the turn manager at the start of each turn.

    Args:
        game: The current GameState to check.

    Returns:
        WinResult if a win condition is met, None otherwise.

    Note:
        This function does NOT modify the game state. The caller is responsible
        for calling game.set_winner() if a WinResult is returned.
    """
    # Skip if game is already over
    if game.is_game_over():
        return None

    win_config = game.rules.win_conditions

    # Check prizes/points taken
    if win_config.all_prizes_taken:
        result = check_prizes_taken(game)
        if result is not None:
            return result

    # Check no Pokemon in play
    if win_config.no_pokemon_in_play:
        result = check_no_pokemon_in_play(game)
        if result is not None:
            return result

    # Check cannot draw (only relevant at draw phase)
    # This is typically called at the start of draw phase, but we check
    # it here for completeness. The turn manager should call this specifically.
    if win_config.cannot_draw:
        result = check_cannot_draw(game)
        if result is not None:
            return result

    return None


def check_prizes_taken(game: GameState) -> WinResult | None:
    """Check if any player has scored enough points to win.

    In point-based mode (default for Mantimon TCG), checks if any player's
    score meets or exceeds the required point count.

    In prize card mode (use_prize_cards=True), checks if any player has
    taken all their prize cards (prizes zone is empty).

    Args:
        game: The current GameState.

    Returns:
        WinResult if a player has won via prizes/points, None otherwise.
    """
    rules = game.rules
    prize_config = rules.prizes

    if prize_config.use_prize_cards:
        # Prize card mode: win when all prize cards are taken
        for player_id, player in game.players.items():
            if player.prizes.is_empty() and game.phase.value != "setup":
                # All prizes taken - this player wins
                opponent_id = game.get_opponent_id(player_id)
                return WinResult(
                    winner_id=player_id,
                    loser_id=opponent_id,
                    end_reason=GameEndReason.PRIZES_TAKEN,
                    reason=f"Player {player_id} took all prize cards",
                )
    else:
        # Point-based mode: win when score reaches required count
        for player_id, player in game.players.items():
            if player.score >= prize_config.count:
                opponent_id = game.get_opponent_id(player_id)
                return WinResult(
                    winner_id=player_id,
                    loser_id=opponent_id,
                    end_reason=GameEndReason.PRIZES_TAKEN,
                    reason=f"Player {player_id} scored {player.score} points "
                    f"(required: {prize_config.count})",
                )

    return None


def check_no_pokemon_in_play(game: GameState) -> WinResult | None:
    """Check if any player has no Pokemon in play.

    A player loses if they have no Pokemon in their active slot and no
    Pokemon on their bench. This is checked after knockouts are resolved.

    Note: During setup phase, this check is skipped as players are still
    placing their initial Pokemon.

    Args:
        game: The current GameState.

    Returns:
        WinResult if a player has lost due to no Pokemon, None otherwise.
    """
    # Skip during setup - players haven't placed Pokemon yet
    if game.phase.value == "setup":
        return None

    for player_id, player in game.players.items():
        if not player.has_pokemon_in_play():
            # This player has no Pokemon - they lose
            opponent_id = game.get_opponent_id(player_id)
            return WinResult(
                winner_id=opponent_id,
                loser_id=player_id,
                end_reason=GameEndReason.NO_POKEMON,
                reason=f"Player {player_id} has no Pokemon in play",
            )

    return None


def check_cannot_draw(game: GameState) -> WinResult | None:
    """Check if the current player cannot draw a card.

    This check is specifically for the scenario where a player must draw
    at the start of their turn but their deck is empty. This should be
    called at the beginning of the draw phase.

    In standard rules, a player loses if they cannot draw at the start
    of their turn. This does not apply to drawing during other phases
    (effects that try to draw from an empty deck just draw nothing).

    Args:
        game: The current GameState.

    Returns:
        WinResult if the current player loses due to empty deck, None otherwise.

    Note:
        This should only be called at the start of draw phase. Outside of
        draw phase, this returns None to avoid false positives.
    """
    # Only check during draw phase
    if game.phase.value != "draw":
        return None

    current_player = game.get_current_player()

    if current_player.deck.is_empty():
        # Current player cannot draw - they lose
        opponent_id = game.get_opponent_id(game.current_player_id)
        return WinResult(
            winner_id=opponent_id,
            loser_id=game.current_player_id,
            end_reason=GameEndReason.DECK_EMPTY,
            reason=f"Player {game.current_player_id} cannot draw (deck empty)",
        )

    return None


def check_turn_limit(game: GameState) -> WinResult | None:
    """Check if the turn limit has been reached.

    When turn_limit_enabled is True, the game ends in various ways when
    the turn limit is reached. This should be called at the start of each
    turn to check if the limit has been exceeded.

    The winner is determined by score:
        - Higher score wins
        - Equal scores result in a draw (winner_id will be empty string)

    Args:
        game: The current GameState.

    Returns:
        WinResult if turn limit reached, None otherwise.

    Note:
        A draw is represented with winner_id="" and end_reason=DRAW.
    """
    win_config = game.rules.win_conditions

    if not win_config.turn_limit_enabled:
        return None

    # Check if we've exceeded the turn limit
    # turn_number counts each player's turn, so at turn_limit+1 we've exceeded
    if game.turn_number <= win_config.turn_limit:
        return None

    # Turn limit reached - determine winner by score
    player_ids = list(game.players.keys())
    if len(player_ids) != 2:
        # Only support 2-player for now
        return None

    player1_id, player2_id = player_ids
    player1 = game.players[player1_id]
    player2 = game.players[player2_id]

    if player1.score > player2.score:
        return WinResult(
            winner_id=player1_id,
            loser_id=player2_id,
            end_reason=GameEndReason.TURN_LIMIT,
            reason=f"Turn limit reached. {player1_id} wins with {player1.score} "
            f"points vs {player2.score}",
        )
    elif player2.score > player1.score:
        return WinResult(
            winner_id=player2_id,
            loser_id=player1_id,
            end_reason=GameEndReason.TURN_LIMIT,
            reason=f"Turn limit reached. {player2_id} wins with {player2.score} "
            f"points vs {player1.score}",
        )
    else:
        # Scores are equal - it's a draw
        # We use DRAW end reason and empty string for winner
        # The "loser" in a draw is arbitrary but we need to provide something
        return WinResult(
            winner_id="",
            loser_id="",
            end_reason=GameEndReason.DRAW,
            reason=f"Turn limit reached. Game ends in a draw ({player1.score} - {player2.score})",
        )


def check_resignation(game: GameState, resigning_player_id: str) -> WinResult:
    """Create a WinResult for when a player resigns.

    Unlike other win conditions, resignation is triggered by a player action
    rather than game state. This is a helper function to create the appropriate
    WinResult.

    Args:
        game: The current GameState.
        resigning_player_id: The ID of the player who is resigning.

    Returns:
        WinResult with the opponent as winner and RESIGNATION reason.

    Raises:
        ValueError: If resigning_player_id is not in the game.
    """
    if resigning_player_id not in game.players:
        raise ValueError(f"Player {resigning_player_id} not found in game")

    opponent_id = game.get_opponent_id(resigning_player_id)

    return WinResult(
        winner_id=opponent_id,
        loser_id=resigning_player_id,
        end_reason=GameEndReason.RESIGNATION,
        reason=f"Player {resigning_player_id} resigned",
    )


def check_timeout(game: GameState, timed_out_player_id: str) -> WinResult:
    """Create a WinResult for when a player times out.

    Similar to resignation, timeout is triggered externally (by a timer)
    rather than by game state. This is a helper function to create the
    appropriate WinResult.

    Args:
        game: The current GameState.
        timed_out_player_id: The ID of the player who timed out.

    Returns:
        WinResult with the opponent as winner and TIMEOUT reason.

    Raises:
        ValueError: If timed_out_player_id is not in the game.
    """
    if timed_out_player_id not in game.players:
        raise ValueError(f"Player {timed_out_player_id} not found in game")

    opponent_id = game.get_opponent_id(timed_out_player_id)

    return WinResult(
        winner_id=opponent_id,
        loser_id=timed_out_player_id,
        end_reason=GameEndReason.TIMEOUT,
        reason=f"Player {timed_out_player_id} timed out",
    )


def apply_win_result(game: GameState, result: WinResult) -> None:
    """Apply a WinResult to the game state.

    This is a convenience function that sets the winner and end reason
    on the game state. It handles the draw case where winner_id is empty.

    Args:
        game: The GameState to update.
        result: The WinResult to apply.
    """
    if result.end_reason == GameEndReason.DRAW:
        # For draws, we set winner_id to None and just set the end_reason
        game.winner_id = None
        game.end_reason = result.end_reason
    else:
        game.set_winner(result.winner_id, result.end_reason)
