import itertools
from collections import defaultdict
from typing import List, Dict, Optional

from server.game.manager import ClientManager
from server.game.message import MessageToPlayer
from server.model.card import Card
from server.model.deck import Deck
from server.model.hand import Hand
from server.model.players import Player, Announce
from server.model.value import Value


class Game:
    def __init__(self,
                 players=None,
                 deck: Deck = Deck(),
                 manager: Optional[ClientManager] = None
                 ):
        if players is None:
            players = []

        self.deck: Deck = deck
        self.players: List[Player] = players
        self.defeats: Dict[Player, int] = defaultdict(lambda: 0)
        self.current_bet: Optional[Hand] = None
        self.manager = manager

    async def message(self, message: MessageToPlayer,
                      *to: Player,
                      extra=None
                      ) -> None:
        if self.manager:
            if not to:
                to = self.players
            for player in to:
                await self.manager.send(player, message, extra)

    @property
    def global_hand(self) -> Hand:
        all_cards = [c for p in self.players for c in p.hand.cards]
        return Hand(cards=all_cards)

    async def new_game(self):
        print(f"Game starting with {self.players}!")
        self.deck.reset()

        while len(self.players) > 1:
            loser = await self.new_turn()

            if self.defeats[loser] == 5:
                print(f"{loser} is eliminated!")
                await self.message(MessageToPlayer.Lose, loser)
                self.players.remove(loser)
            else:
                print(f"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!")

        winner = self.players[0]
        await self.message(MessageToPlayer.Win, winner)
        await self.message(MessageToPlayer.WinnerIs, extra=winner)
        print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!")

    async def new_turn(self) -> Player:
        """
        Runs a turn of the game.

        :return: the player that lost this turn.
        """
        # Distribution
        self.deck.reset()

        await self.message(MessageToPlayer.WaitTurn)
        for current_player in self.players:
            current_player.clear()
            count_player_cards = self.defeats[current_player] + 1
            print(f"Drawing {count_player_cards} card(s) for {current_player}: ", end="")
            for i in range(count_player_cards):
                card = self.deck.random_card()
                current_player.give(card)
                print(f"{card}")

            await self.message(MessageToPlayer.GiveHand, current_player, extra=current_player.hand.json())
            print(f"Cards sent.")

        # Tour
        self.current_bet = None
        last_player = None
        for current_player in itertools.cycle(self.players):
            loser = await self.play_turn(current_player, last_player)
            if loser is not None:
                self.players.remove(loser)
                self.players.insert(0, loser)
                await self.message(MessageToPlayer.LoseRound, extra=loser.name)
                return loser
            last_player = current_player

        # TODO: Put next first player first of list

    def is_menteur(self, bet: Hand):
        """
        Is this bet a menteur?
        :param bet:
        :return:
        """

        to_scan = [Card(value=c.value) for c in self.global_hand.cards.copy()]  # Keep only values
        to_find = [Card(value=c.value) for c in bet.cards]
        menteur = False

        for card in to_find:
            if card in to_scan:
                to_scan.remove(card)
                continue
            else:
                print(f"Missing {card}!")
                menteur = True
        if menteur:
            print(f"Didn't find {bet} in {self.global_hand}: MENTEUR!")
        return menteur

    def count_defeat(self, loser):
        self.defeats[loser] += 1
        print(f"Total defeats for {loser}: {self.defeats[loser]}")

    def add_player(self, player: Player):
        """
        Adds a player to the game.
        :return: True if the player was accepted (unique names).
        """
        if any([s.name == player.name for s in self.players]):
            return False
        else:
            self.players.append(player)
            return True

    async def play_turn(self, current_player: Player, last_player: Player) -> Optional[Player]:
        """
        Runs a turn, eventually returning a loser.

        :param current_player:
        :param last_player:
        :return:
        """
        print(f"| {current_player}'s turn >")

        if not self.current_bet:  # First player, has to bet something
            print("Game: First awaiting current bet")
            await self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet)
            while not self.current_bet:  # Ask a valid bet
                print("Game: While no bet, awaiting")
                announce = await current_player.announce(self.current_bet)
                if announce.bet:
                    self.current_bet = announce.bet
                    print(f"{current_player} starts the round: {self.current_bet}")
                    print("Game: Awaiting start announce")
                    await self.message(MessageToPlayer.Announce,
                                       extra={"player": current_player.name, "announce": announce.json()})
                else:
                    print(f"You cannot say Menteur on first round, {current_player}!")

        else:  # Next player, announce or menteur

            # Wait, is the announce the last possible one?
            if len(self.current_bet.cards) == 4 and all([c.value is Value.Ace for c in self.current_bet.cards]):
                print("CARRE D'AS!")
                announce = Announce()  # MENTEUR obligatoire
            else:
                print("Game: Awaiting bet")
                await self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet)
                announce = await current_player.announce(self.current_bet)

            if announce.bet:
                while announce.bet.value() < self.current_bet.value():  # Bad announce!
                    print(f"Invalid bet by {current_player}, {announce.bet} < {self.current_bet}!")
                    announce = await current_player.announce(self.current_bet)
                self.current_bet = announce.bet

                # Valid bet:
                print(f" {current_player} bets {self.current_bet}.")
                self.message(MessageToPlayer.Announce, extra={"player": current_player, "announce": announce})

            else:  # Menteur! Who lost the round?
                menteur = self.is_menteur(self.current_bet)
                loser = last_player if menteur else current_player
                self.count_defeat(loser)

                print(f"{current_player} says Menteur... {'Bravo!' if menteur else 'FAIL!'}")
                return loser