"""Session implementations for Connect 4."""

from typing import Optional, Tuple

from base_models import GameOutcome, GameStatus, Gamestats, Player
from api import IGame, IGameSession
from game.engine import Connect4Game


class DefaultGameSession(IGameSession):
    """In-memory default session implementation."""

    def __init__(self) -> None:
        self._players: Optional[Tuple[Player, Player]] = None
        self._stats: Optional[Tuple[Gamestats, Gamestats]] = None
        self._current_game: Optional[IGame] = None
        self._processed_game_ids: set[str] = set()

    def set_players(self, players: Tuple[Player, Player]) -> None:
        """Configure the session players.

        Args:
            players: Players in seat order as ``(player_one, player_two)``.

        Raises:
            ValueError: If player ids are not ``1`` and ``2`` in seat order.
        """
        player_one, player_two = players
        if player_one.id != 1 or player_two.id != 2:
            raise ValueError("Player ids must be 1 and 2 in seat order.")

        self._players = players

        # Preserve aggregate stats once they exist so the UI can reconfigure
        # bot difficulty from the home screen without resetting results.
        if self._stats is None:
            self._stats = (
                Gamestats(player_name=player_one.name, player_id=player_one.id),
                Gamestats(player_name=player_two.name, player_id=player_two.id),
            )
            self._processed_game_ids = set()
        else:
            p1_stats, p2_stats = self._stats
            p1_stats.player_name = player_one.name
            p2_stats.player_name = player_two.name

        self._current_game = None

    def start_new_game(self) -> IGame:
        """Create a new game for this session's configured players.

        If the current game has completed and was not yet aggregated into
        session stats, it is aggregated before creating the next game.

        Returns:
            Newly created game instance.

        Raises:
            ValueError: If players are not configured.
        """
        if self._players is None:
            raise ValueError("Players are not set for this session yet.")

        self._sync_stats_from_current_game()
        self._current_game = Connect4Game(self._players)
        return self._current_game

    def get_current_game(self) -> Optional[IGame]:
        """Return the current game if backend game logic exists.

        Returns:
            The active game, or ``None`` when no game has started.
        """
        self._sync_stats_from_current_game()
        return self._current_game

    def get_player_session_stats(self) -> Tuple[Gamestats, Gamestats]:
        """Return per-player aggregate stats for this in-memory session.

        Returns:
            Stats for player one and player two.

        Raises:
            ValueError: If players are not configured.
        """
        if self._stats is None:
            raise ValueError("Players are not set for this session yet.")
        self._sync_stats_from_current_game()
        return self._stats

    def _sync_stats_from_current_game(self) -> None:
        """Aggregate stats from completed games exactly once."""
        if self._current_game is None or self._stats is None:
            return

        game_data = self._current_game.data()
        if game_data.status != GameStatus.COMPLETED:
            return
        if game_data.game_id in self._processed_game_ids:
            return

        p1_stats, p2_stats = self._stats
        p1_stats.games_played += 1
        p2_stats.games_played += 1

        if game_data.outcome == GameOutcome.PLAYER_1_WIN:
            p1_stats.games_won += 1
            p2_stats.games_lost += 1
        elif game_data.outcome == GameOutcome.PLAYER_2_WIN:
            p2_stats.games_won += 1
            p1_stats.games_lost += 1
        elif game_data.outcome == GameOutcome.DRAW:
            p1_stats.games_draw += 1
            p2_stats.games_draw += 1

        self._processed_game_ids.add(game_data.game_id)


_ACTIVE_SESSION: IGameSession = DefaultGameSession()


def create_players_in_new_session(players: Tuple[Player, Player]) -> IGameSession:
    """Create players on a new implicit session.

    Args:
        players: Player one and player two in seat order.

    Returns:
        The newly created session instance.
    """
    global _ACTIVE_SESSION
    _ACTIVE_SESSION = DefaultGameSession()
    _ACTIVE_SESSION.set_players(players)
    return _ACTIVE_SESSION


def get_active_session_impl() -> IGameSession:
    """Return the currently active implicit session.

    Returns:
        The active session instance.
    """
    return _ACTIVE_SESSION
