"""Visibility filtering for client-safe game state views.

This module implements hidden information filtering to prevent cheating in
multiplayer games. It creates filtered views of the game state that only
expose information a specific player is allowed to see.

SECURITY CRITICAL: This module is the primary defense against information
leakage. All game state sent to clients MUST go through this filter.

Hidden Information (NEVER expose to opponent):
    - Hand contents
    - Deck contents and order
    - Prize card contents (until taken)
    - Energy deck order

Public Information (always visible):
    - Active and benched Pokemon (full details including damage, status, energy)
    - Discard piles (full contents)
    - Stadium in play
    - Scores, turn info, phase, game state
    - Zone counts (hand size, deck size, prize count)
    - Energy zone (available energy)

Usage:
    from app.core.visibility import get_visible_state, VisibleGameState

    # Get filtered view for a specific player
    visible = get_visible_state(game, "player1")

    # Safe to serialize and send to client
    json_data = visible.model_dump_json()
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel, Field

from app.core.enums import GameEndReason, TurnPhase
from app.core.models.card import CardDefinition, CardInstance

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


class VisibleZone(BaseModel):
    """A zone with visibility-appropriate information.

    For hidden zones (deck, hand, prizes), only the count is shown.
    For public zones (active, bench, discard), full card data is included.

    Attributes:
        count: Number of cards in the zone.
        cards: List of visible cards (empty for hidden zones).
        zone_type: Type of zone for identification.
    """

    count: int = 0
    cards: list[CardInstance] = Field(default_factory=list)
    zone_type: str = "generic"


class VisiblePlayerState(BaseModel):
    """Player state with visibility filtering applied.

    Contains full information for zones the viewing player can see,
    and only counts for hidden zones.

    Attributes:
        player_id: The player's ID.
        is_current_player: Whether this is the viewing player.
        deck_count: Number of cards in deck (order hidden).
        hand: Full hand if own player, just count if opponent.
        active: Active Pokemon with full details (public).
        bench: Benched Pokemon with full details (public).
        discard: Full discard pile contents (public).
        prizes_count: Number of prize cards remaining.
        energy_deck_count: Number of cards in energy deck.
        energy_zone: Available energy to attach (public).
        score: Current score/points.
        gx_attack_used: Whether GX attack has been used.
        vstar_power_used: Whether VSTAR power has been used.
    """

    player_id: str
    is_current_player: bool = False

    # Hidden zones - counts only (for opponent), full for self
    deck_count: int = 0
    hand: VisibleZone = Field(default_factory=lambda: VisibleZone(zone_type="hand"))
    prizes_count: int = 0
    energy_deck_count: int = 0

    # Public zones - full details
    active: VisibleZone = Field(default_factory=lambda: VisibleZone(zone_type="active"))
    bench: VisibleZone = Field(default_factory=lambda: VisibleZone(zone_type="bench"))
    discard: VisibleZone = Field(default_factory=lambda: VisibleZone(zone_type="discard"))
    energy_zone: VisibleZone = Field(default_factory=lambda: VisibleZone(zone_type="energy_zone"))

    # Game state
    score: int = 0
    gx_attack_used: bool = False
    vstar_power_used: bool = False


class VisibleGameState(BaseModel):
    """Game state filtered for a specific player's view.

    This is the safe-to-send-to-client version of GameState. It contains
    only information that the viewing player is allowed to see.

    Attributes:
        game_id: Unique game identifier.
        viewer_id: The player ID this view is for.
        players: Dict of player_id -> VisiblePlayerState.
        current_player_id: Whose turn it is.
        turn_number: Current turn number.
        phase: Current turn phase.
        is_my_turn: Whether it's the viewer's turn.
        winner_id: Winner if game is over.
        end_reason: Why the game ended.
        stadium_in_play: Current stadium card (public).
        stadium_owner_id: Player who played the current stadium (public).
        forced_action: Current forced action, if any.
        card_registry: Card definitions (needed to display cards).
    """

    game_id: str
    viewer_id: str

    # Player states
    players: dict[str, VisiblePlayerState] = Field(default_factory=dict)

    # Turn tracking
    current_player_id: str = ""
    turn_number: int = 0
    phase: TurnPhase = TurnPhase.SETUP
    is_my_turn: bool = False

    # Game end state
    winner_id: str | None = None
    end_reason: GameEndReason | None = None

    # Shared state
    stadium_in_play: CardInstance | None = None
    stadium_owner_id: str | None = None

    # Forced action (visible to know what's required)
    forced_action_player: str | None = None
    forced_action_type: str | None = None
    forced_action_reason: str | None = None

    # Card definitions for display
    card_registry: dict[str, CardDefinition] = Field(default_factory=dict)


def _create_visible_zone(
    cards: list[CardInstance],
    zone_type: str,
    show_contents: bool,
) -> VisibleZone:
    """Create a VisibleZone with appropriate visibility.

    Args:
        cards: The cards in the zone.
        zone_type: Type identifier for the zone.
        show_contents: If True, include full card data. If False, only count.

    Returns:
        VisibleZone with count and optionally cards.
    """
    return VisibleZone(
        count=len(cards),
        cards=list(cards) if show_contents else [],
        zone_type=zone_type,
    )


def _filter_player_state(
    game: GameState,
    player_id: str,
    viewer_id: str,
) -> VisiblePlayerState:
    """Create a visibility-filtered player state.

    Args:
        game: The full game state.
        player_id: The player whose state we're filtering.
        viewer_id: The player who will view this state.

    Returns:
        VisiblePlayerState with appropriate filtering.
    """
    player = game.players[player_id]
    is_self = player_id == viewer_id

    # Hand: visible to self, count only to opponent
    hand = _create_visible_zone(
        cards=player.hand.cards,
        zone_type="hand",
        show_contents=is_self,
    )

    # Active: always public (this is the battlefield)
    active = _create_visible_zone(
        cards=player.active.cards,
        zone_type="active",
        show_contents=True,
    )

    # Bench: always public (this is the battlefield)
    bench = _create_visible_zone(
        cards=player.bench.cards,
        zone_type="bench",
        show_contents=True,
    )

    # Discard: always public
    discard = _create_visible_zone(
        cards=player.discard.cards,
        zone_type="discard",
        show_contents=True,
    )

    # Energy zone: always public (available energy to attach)
    energy_zone = _create_visible_zone(
        cards=player.energy_zone.cards,
        zone_type="energy_zone",
        show_contents=True,
    )

    return VisiblePlayerState(
        player_id=player_id,
        is_current_player=is_self,
        deck_count=len(player.deck),
        hand=hand,
        prizes_count=len(player.prizes),
        energy_deck_count=len(player.energy_deck),
        active=active,
        bench=bench,
        discard=discard,
        energy_zone=energy_zone,
        score=player.score,
        gx_attack_used=player.gx_attack_used,
        vstar_power_used=player.vstar_power_used,
    )


def get_visible_state(game: GameState, viewer_id: str) -> VisibleGameState:
    """Create a visibility-filtered game state for a specific player.

    This is the main entry point for visibility filtering. It creates a
    VisibleGameState that only contains information the viewer is allowed
    to see.

    SECURITY: Always use this function before sending game state to clients.

    Args:
        game: The full game state.
        viewer_id: The player ID requesting the view.

    Returns:
        VisibleGameState safe to send to the client.

    Raises:
        ValueError: If viewer_id is not a player in the game.
    """
    if viewer_id not in game.players:
        raise ValueError(f"Viewer {viewer_id} is not a player in game {game.game_id}")

    # Filter each player's state
    visible_players = {pid: _filter_player_state(game, pid, viewer_id) for pid in game.players}

    # Extract forced action info (if any) - only shows the current/first action in queue
    forced_action_player = None
    forced_action_type = None
    forced_action_reason = None
    current_forced = game.get_current_forced_action()
    if current_forced:
        forced_action_player = current_forced.player_id
        forced_action_type = current_forced.action_type
        forced_action_reason = current_forced.reason

    return VisibleGameState(
        game_id=game.game_id,
        viewer_id=viewer_id,
        players=visible_players,
        current_player_id=game.current_player_id,
        turn_number=game.turn_number,
        phase=game.phase,
        is_my_turn=game.current_player_id == viewer_id,
        winner_id=game.winner_id,
        end_reason=game.end_reason,
        stadium_in_play=game.stadium_in_play,
        stadium_owner_id=game.stadium_owner_id,
        forced_action_player=forced_action_player,
        forced_action_type=forced_action_type,
        forced_action_reason=forced_action_reason,
        card_registry=game.card_registry,
    )


def get_spectator_state(game: GameState) -> VisibleGameState:
    """Create a visibility-filtered game state for spectators.

    Spectators see the same as any player regarding public information,
    but cannot see any player's hidden information.

    Args:
        game: The full game state.

    Returns:
        VisibleGameState with all hands/decks hidden.
    """
    # Use a fake viewer_id that won't match any player
    # This ensures all hands are hidden
    spectator_id = "__spectator__"

    # Filter each player's state as if viewed by spectator
    visible_players = {}
    for pid in game.players:
        player = game.players[pid]

        # All hands hidden for spectators
        hand = _create_visible_zone(
            cards=player.hand.cards,
            zone_type="hand",
            show_contents=False,
        )

        # Public zones still visible
        active = _create_visible_zone(
            cards=player.active.cards,
            zone_type="active",
            show_contents=True,
        )
        bench = _create_visible_zone(
            cards=player.bench.cards,
            zone_type="bench",
            show_contents=True,
        )
        discard = _create_visible_zone(
            cards=player.discard.cards,
            zone_type="discard",
            show_contents=True,
        )
        energy_zone = _create_visible_zone(
            cards=player.energy_zone.cards,
            zone_type="energy_zone",
            show_contents=True,
        )

        visible_players[pid] = VisiblePlayerState(
            player_id=pid,
            is_current_player=False,
            deck_count=len(player.deck),
            hand=hand,
            prizes_count=len(player.prizes),
            energy_deck_count=len(player.energy_deck),
            active=active,
            bench=bench,
            discard=discard,
            energy_zone=energy_zone,
            score=player.score,
            gx_attack_used=player.gx_attack_used,
            vstar_power_used=player.vstar_power_used,
        )

    # Extract forced action info - only shows the current/first action in queue
    forced_action_player = None
    forced_action_type = None
    forced_action_reason = None
    current_forced = game.get_current_forced_action()
    if current_forced:
        forced_action_player = current_forced.player_id
        forced_action_type = current_forced.action_type
        forced_action_reason = current_forced.reason

    return VisibleGameState(
        game_id=game.game_id,
        viewer_id=spectator_id,
        players=visible_players,
        current_player_id=game.current_player_id,
        turn_number=game.turn_number,
        phase=game.phase,
        is_my_turn=False,
        winner_id=game.winner_id,
        end_reason=game.end_reason,
        stadium_in_play=game.stadium_in_play,
        stadium_owner_id=game.stadium_owner_id,
        forced_action_player=forced_action_player,
        forced_action_type=forced_action_type,
        forced_action_reason=forced_action_reason,
        card_registry=game.card_registry,
    )
