import asyncio
import json
from typing import List, Dict, Optional

from pydantic.main import BaseModel
from socketio import AsyncServer

from server.game.manager import ClientManager
from server.game.message import MessageToPlayer, MessageFromPlayer
from server.model.game import Game
from server.model.known import CARRE_ACE
from server.model.players import Player, Announce


class Metadata(BaseModel):
    ready: bool = False
    sid: str = ""
    last_announce: Optional[Announce] = None
    fresh_announce: bool = True


class LobbyManager(ClientManager):
    """ A ClientManager that handles a lobby, then orchestrates games.
    """

    def __init__(self, sio: AsyncServer):
        super().__init__()
        self.lobby: Dict[str, Player] = {}
        self.metadata: Dict[str, Metadata] = {}
        self.games: List[Game] = []
        self.sio = sio

    @property
    def nb_ready(self):
        return len(self.players_ready)

    @property
    def players_ready(self):
        return [self.lobby[k] for k, m in self.metadata.items() if m.ready]

    def new_player(self, player: Player, sid: str) -> str:
        self.lobby[player.name] = player
        self.players.append(player)
        self.metadata[player.name] = Metadata(sid=sid)
        print(f"Added {player} to a lobby with {len(self.lobby)} players.")
        return f"Bienvenu, {player}! Il y a {len(self.lobby)} joueurs en ligne."

    def wants_to_play(self, player: Player):
        """
        Notes that a player wants to play, starting a game if others too.
        """
        self.metadata[player.name].ready = True
        print(f"Lobby | {player} wants to play!")

        ready = self.players_ready
        return ready

    async def maybe_start_game(self):
        print("Should we start a game?")
        ready = self.players_ready
        if len(ready) > 1:
            await self.start_game(ready)
            print(f"Lobby: Game started. with {ready}.")

    def just_announced(self, player: Player, announce: Announce):
        meta = self.metadata[player.name]
        meta.last_announce = announce
        meta.fresh_announce = True

    async def start_game(self, players: Optional[List[Player]] = None, max_players=2):
        if not players:
            players = self.players_ready[:max_players]

        game = Game(players, manager=self)
        self.games.append(game)

        for p in players:
            self.metadata[p.name].ready = False

        print(f"Game started : {' vs '.join([p.name for p in players])}")
        await game.new_game()

    def reset(self):
        players = len(self.players)
        games = len(self.games)
        msg = f"Resetting! sorry for the {players} players / {games} games..."
        print(msg)
        for p in self.players:
            m = self.metadata[p.name]
            self.send(p, MessageToPlayer.Reset)
            self.sio.disconnect(m.sid)
            self.lobby.clear()
            self.players.clear()
            self.metadata.clear()
            self.games.clear()
        print(f"Reset done, {players} players, {games} games.")
        return msg

    async def send(self, to: Player, message: MessageToPlayer, extras=None):
        sid = self.metadata[to.name].sid
        data = {"message": str(message.name)}
        if extras:
            data["extra"] = extras

        print(f"MSGOUT|{sid} ({to.name}), {data}")
        await self.sio.emit('messageChannel', json.dumps(data), room=sid)

    async def handle_message(self, sid, data):
        message = None
        extras = {"players": [p.name for p in self.players]}
        sender = self.which_player(sid)

        print(f"MSGIN| {sid} ({sender.name}): {data}.")

        for option in MessageFromPlayer:
            if option.value in data:

                if option == MessageFromPlayer.Waiting:
                    self.metadata[sender.name].ready = False

                    print(f"Lobby| Player {sender.name} waiting")

                    message = MessageToPlayer.ReadyToStart \
                        if len(self.players_ready) > 1 else MessageToPlayer.Waiting

                elif option == MessageFromPlayer.Ready:
                    print(f"Lobby|Player {sender.name} ready")

                    gamers = self.wants_to_play(player=sender)
                    if gamers:
                        print(f"Game ready! gamers={gamers}")
                        message = MessageToPlayer.NewGame
                        extras["playersPlaying"] = [p.name for p in gamers]

                    else:
                        print(f"Still alone ready!")
                        message = MessageToPlayer.ReadyToStart
                        extras["playersReady"] = [p.name for p in self.players_ready]


                elif option == MessageFromPlayer.Bet:
                    # FIXME vraie annonce, pas juste carre d'as lol
                    announce = Announce(bet=CARRE_ACE)
                    self.just_announced(player=sender,
                                        announce=announce)

                    print(f"Lobby| Player {sender.name} just announced {announce}.")

                    extras["youBet"] = announce.bet

                elif option == MessageFromPlayer.Menteur:
                    self.just_announced(player=sender, announce=Announce())
                    extras["youBet"] = False

        if message:
            print("Creating game task...")
            game = asyncio.create_task(self.maybe_start_game())
            print("Sending message...")
            await self.send(sender, message, extras)
            print("Awaiting game...")
            await game

        print("LOBBY: Message handled.")

    def which_player(self, sid) -> Player:
        sender: Optional[Player] = None
        for name, metadata in self.metadata.items():
            if metadata.sid == sid:
                sender = self.lobby[name]
        return sender

    def game_with(self, player: Player) -> Game:
        return [g for g in self.games if player in g.players][0]

    async def send_waiting_for(self, player: Player):
        game = self.game_with(player)
        # FIXME: Seems to block here, game doesn't move past Asking for...
        # BET
        # MSGIN| c38442e3785f4c2eb13e95368bfaa151 (Sumatran Elephant): BET.
        # Lobby| Player Sumatran Elephant just announced bet=Ace of ♥|Ace of ♣|Ace of ♦|Ace of ♠.
        # LOBBY: Message handled.
        # received event "message" from c38442e3785f4c2eb13e95368bfaa151 [/]
        # INFO:     received event "message" from c38442e3785f4c2eb13e95368bfaa151 [/]
        # INFO:     127.0.0.1:51452 - "POST /socket.io/?EIO=3&transport=polling&t=N6J1ZS7&sid=3c5e8962c4db465aaf2fd916bea2acc9 HTTP/1.1" 200 OK
        # [WS] Message  MENTEUR
        # MSGIN| 3c5e8962c4db465aaf2fd916bea2acc9 (Chihuahua): MENTEUR.
        # LOBBY: Message handled.
        # received
        await self.send(player, MessageToPlayer.YourTurn, extras={"bet": game.current_bet.json()})
        for p in [p for p in game.players if p != player]:
            await self.send(p, MessageToPlayer.Waiting, extras={"waitingFor": p.name})
