"""Game models for Mantimon TCG.

This module defines models for game state management:
- ActiveGame: In-progress games (Postgres backup for Redis)
- GameHistory: Completed games with replay data

Example:
    # Creating an active game
    game = ActiveGame(
        game_type=GameType.CAMPAIGN,
        player1_id=user.id,
        npc_id="grass_trainer_1",
        rules_config={"prize_count": 4},
        game_state=serialized_state
    )

    # Recording game history
    history = GameHistory(
        game_type=GameType.CAMPAIGN,
        player1_id=user.id,
        npc_id="grass_trainer_1",
        winner_id=user.id,
        end_reason=EndReason.PRIZES_TAKEN,
        turn_count=15
    )
"""

from datetime import UTC, datetime
from enum import Enum
from typing import TYPE_CHECKING
from uuid import UUID

from sqlalchemy import DateTime, ForeignKey, Index, Integer, String
from sqlalchemy import Enum as SQLEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.db.base import Base

if TYPE_CHECKING:
    from app.db.models.user import User


class GameType(str, Enum):
    """Type of game being played.

    Attributes:
        CAMPAIGN: Single-player campaign match vs NPC.
        FREEPLAY: Casual unranked match.
        RANKED: Competitive ranked match.
        PRACTICE: Practice mode (no rewards).
    """

    CAMPAIGN = "campaign"
    FREEPLAY = "freeplay"
    RANKED = "ranked"
    PRACTICE = "practice"


class EndReason(str, Enum):
    """Reason a game ended.

    Attributes:
        PRIZES_TAKEN: Winner took all prize cards.
        NO_POKEMON: Opponent had no Pokemon left in play.
        CANNOT_DRAW: Opponent couldn't draw at turn start.
        RESIGNATION: Player resigned/quit.
        TIMEOUT: Player timed out.
        TURN_LIMIT: Game reached turn limit (draw possible).
        DISCONNECTION: Player disconnected and didn't reconnect.
        DRAW: Game ended in a draw.
    """

    PRIZES_TAKEN = "prizes_taken"
    NO_POKEMON = "no_pokemon"
    CANNOT_DRAW = "cannot_draw"
    RESIGNATION = "resignation"
    TIMEOUT = "timeout"
    TURN_LIMIT = "turn_limit"
    DISCONNECTION = "disconnection"
    DRAW = "draw"


class ActiveGame(Base):
    """In-progress game state (Postgres backup).

    This is the durable storage for active games. The primary game state
    lives in Redis for fast access during gameplay. Postgres serves as
    backup and is updated at turn boundaries.

    Attributes:
        id: Unique game identifier (UUID).
        game_type: Type of game (campaign, freeplay, ranked).
        player1_id: First player's user ID.
        player2_id: Second player's user ID (null for campaign).
        npc_id: NPC opponent ID for campaign games.
        rules_config: JSONB of RulesConfig settings.
        game_state: JSONB of serialized GameState.
        turn_number: Current turn number.
        started_at: When the game started.
        last_action_at: Timestamp of last action.
        turn_deadline: When current turn times out.

    Notes:
        - Redis is primary, Postgres is backup
        - Updated at turn boundaries (not every action)
        - On server restart, active games load from Postgres -> Redis
    """

    __tablename__ = "active_games"

    # Game type
    game_type: Mapped[GameType] = mapped_column(
        SQLEnum(GameType, name="game_type", create_constraint=True),
        nullable=False,
        doc="Type of game (campaign, freeplay, ranked)",
    )

    # Players
    player1_id: Mapped[UUID] = mapped_column(
        ForeignKey("users.id", ondelete="CASCADE"),
        nullable=False,
        doc="First player's user ID",
    )
    player2_id: Mapped[UUID | None] = mapped_column(
        ForeignKey("users.id", ondelete="SET NULL"),
        nullable=True,
        doc="Second player's user ID (null for campaign)",
    )
    npc_id: Mapped[str | None] = mapped_column(
        String(100),
        nullable=True,
        doc="NPC opponent ID for campaign games",
    )

    # Game configuration and state (JSONB)
    rules_config: Mapped[dict] = mapped_column(
        JSONB,
        default=dict,
        nullable=False,
        doc="RulesConfig settings as JSONB",
    )
    game_state: Mapped[dict] = mapped_column(
        JSONB,
        default=dict,
        nullable=False,
        doc="Serialized GameState as JSONB",
    )
    turn_number: Mapped[int] = mapped_column(
        Integer,
        default=0,
        nullable=False,
        doc="Current turn number",
    )

    # Timing
    started_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        default=lambda: datetime.now(UTC),
        nullable=False,
        doc="When the game started",
    )
    last_action_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        default=lambda: datetime.now(UTC),
        nullable=False,
        doc="Timestamp of last action",
    )
    turn_deadline: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="When current turn times out",
    )

    # Relationships
    player1: Mapped["User"] = relationship(
        "User",
        foreign_keys=[player1_id],
        lazy="selectin",
    )
    player2: Mapped["User | None"] = relationship(
        "User",
        foreign_keys=[player2_id],
        lazy="selectin",
    )

    # Indexes
    __table_args__ = (
        Index("ix_active_games_player1", "player1_id"),
        Index("ix_active_games_player2", "player2_id"),
        Index("ix_active_games_players", "player1_id", "player2_id"),
    )

    def __repr__(self) -> str:
        return f"<ActiveGame(id={self.id!r}, type={self.game_type}, turn={self.turn_number})>"


class GameHistory(Base):
    """Completed game record.

    Stores the result of finished games including replay data
    for future replay feature.

    Attributes:
        id: Unique identifier (UUID).
        game_type: Type of game that was played.
        player1_id: First player's user ID.
        player2_id: Second player's user ID (null for campaign).
        npc_id: NPC opponent ID for campaign games.
        winner_id: User ID of the winner (null for draws).
        winner_is_npc: True if NPC won (campaign games).
        end_reason: How the game ended.
        turn_count: Total turns played.
        duration_seconds: Game duration in seconds.
        replay_data: JSONB containing action log for replays.
        played_at: When the game was completed.

    Notes:
        - Replay data captures all actions from game start
        - Indexes optimize leaderboard and history queries
    """

    __tablename__ = "game_history"

    # Game type
    game_type: Mapped[GameType] = mapped_column(
        SQLEnum(GameType, name="game_type", create_constraint=True),
        nullable=False,
        doc="Type of game that was played",
    )

    # Players
    # NOTE: nullable=True to allow SET NULL on cascade when user is deleted.
    # Game history is preserved even after user deletion for leaderboards.
    player1_id: Mapped[UUID | None] = mapped_column(
        ForeignKey("users.id", ondelete="SET NULL"),
        nullable=True,
        doc="First player's user ID (nullable for preserved history)",
    )
    player2_id: Mapped[UUID | None] = mapped_column(
        ForeignKey("users.id", ondelete="SET NULL"),
        nullable=True,
        doc="Second player's user ID",
    )
    npc_id: Mapped[str | None] = mapped_column(
        String(100),
        nullable=True,
        doc="NPC opponent ID for campaign games",
    )

    # Result
    winner_id: Mapped[UUID | None] = mapped_column(
        ForeignKey("users.id", ondelete="SET NULL"),
        nullable=True,
        doc="Winner's user ID (null for draws or NPC wins)",
    )
    winner_is_npc: Mapped[bool] = mapped_column(
        default=False,
        nullable=False,
        doc="True if NPC won (campaign games)",
    )
    end_reason: Mapped[EndReason] = mapped_column(
        SQLEnum(EndReason, name="end_reason", create_constraint=True),
        nullable=False,
        doc="How the game ended",
    )

    # Statistics
    turn_count: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        doc="Total turns played",
    )
    duration_seconds: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        doc="Game duration in seconds",
    )

    # Replay data (captures all actions)
    replay_data: Mapped[dict | None] = mapped_column(
        JSONB,
        nullable=True,
        doc="Action log for replay feature",
    )

    # Timestamp
    played_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        default=lambda: datetime.now(UTC),
        nullable=False,
        doc="When the game was completed",
    )

    # Relationships
    # NOTE: passive_deletes=True lets database handle ON DELETE SET NULL
    player1: Mapped["User | None"] = relationship(
        "User",
        foreign_keys=[player1_id],
        lazy="selectin",
        passive_deletes=True,
    )
    player2: Mapped["User | None"] = relationship(
        "User",
        foreign_keys=[player2_id],
        lazy="selectin",
        passive_deletes=True,
    )
    winner: Mapped["User | None"] = relationship(
        "User",
        foreign_keys=[winner_id],
        lazy="selectin",
        passive_deletes=True,
    )

    # Indexes for leaderboard and history queries
    __table_args__ = (
        Index("ix_game_history_player1", "player1_id"),
        Index("ix_game_history_player2", "player2_id"),
        Index("ix_game_history_winner", "winner_id"),
        Index("ix_game_history_played_at", "played_at"),
        Index("ix_game_history_type_played", "game_type", "played_at"),
    )

    @property
    def is_draw(self) -> bool:
        """Check if game ended in a draw.

        Returns:
            True if no winner and end reason is DRAW.
        """
        return self.end_reason == EndReason.DRAW

    def __repr__(self) -> str:
        return (
            f"<GameHistory(id={self.id!r}, type={self.game_type}, "
            f"turns={self.turn_count}, end={self.end_reason})>"
        )
