from typing import Optional, List, Any, Tuple
from unittest import TestCase

from server.game.manager import ClientManager
from server.game.message import MessageToPlayer
from server.model.value import Value
from server.model.deck import Deck
from server.model.game import Game
from server.model.hand import Hand
from server.model.hands import pair, brelan
from server.model.known import CARRE_ACE
from server.model.players import Announce, Player


class MockPlayer(Player):

    def __init__(self,
                 name: str = None,
                 bets: List[Optional[Hand]] = None
                 ):
        super().__init__(name)

        if bets is None:
            bets = []
        self.bets: List[Optional[Hand]] = bets
        self.messages: List[Tuple[MessageToPlayer, Any]] = []

    def count(self, msg: MessageToPlayer) -> int:
        return len([m for (m, e) in self.messages if m is msg])

    async def announce(self, current_bet: Optional[Hand]) -> Announce:
        return Announce(bet=self.bets.pop() if self.bets else CARRE_ACE)

    def receive(self, message: MessageToPlayer, extra: Optional[Any] = None):
        self.messages.append((message, extra))

    def print_msgs(self) -> str:
        return '|'.join([f"{m}[{e}]" if e else str(m) for (m, e) in self.messages])


class MockManager(ClientManager):
    def __init__(self, players: List[MockPlayer]):
        super().__init__()
        self.players = players

    def send(self, to: Player, message: MessageToPlayer, extra=None):
        if isinstance(to, MockPlayer):
            to.receive(message, extra)
            print(f"Sent {message} {'(' + str(extra) + ') ' if extra else ''}to {to}")


class TestManager(TestCase):

    def setUp(self) -> None:
        self.j1 = MockPlayer("j1", [pair(Value.Ace)])
        self.j2 = MockPlayer("j2", [brelan(Value.Two)])
        self.manager = MockManager([self.j1, self.j2])
        cards = []
        cards.extend(self.j1.bets[0].cards)
        cards.extend(self.j2.bets[0].cards)
        self.game = Game(players=[self.j1, self.j2],
                         deck=Deck(cards=cards),
                         manager=self.manager)

    async def test_turn_messages(self):
        await self.game.new_turn()
        self.assertGot(self.j1, MessageToPlayer.LoseRound, extra=self.j1.name, msg=f"J1 should hear round lost.")
        self.assertGot(self.j2, MessageToPlayer.LoseRound, extra=self.j1.name, msg=f"J2 should hear round lost by j1.")
        for player in [self.j1, self.j2]:
            self.assertGot(player, MessageToPlayer.LoseRound, msg="End of round not announced")

    async def test_game_messages(self):
        await self.game.new_game()
        self.assertEqual(len([m for m in self.j1.messages if m is MessageToPlayer.LoseRound]), 5,
                         f"{self.j1} should lose 5 rounds: {'|'.join([str(m) for m in self.j1.messages])}")

        self.assertEqual(self.j1.count(MessageToPlayer.YourTurn), 6,
                         f"{self.j1} should play 6 rounds: {self.j1.print_msgs()}")

        self.assertEqual(self.j2.count(MessageToPlayer.YourTurn), 1,
                         f"{self.j2} should play 1 rounds: {self.j2.print_msgs()}")

        self.assertEqual(len([m for m in self.j2.messages if m is MessageToPlayer.LoseRound]), 0,
                         f"{self.j2} should lose 0 rounds: {'|'.join([str(m) for m in self.j2.messages])}")
        for player in [self.j1, self.j2]:
            self.assertEqual(player.count(MessageToPlayer.Announce), 7,
                             f"{player} should see 7 announces: {player.print_msgs()}")
            self.assertGot(player, MessageToPlayer.WinnerIs, self.j1, "Winner J1 not announced")
        self.assertIn(MessageToPlayer.Win, self.j1.messages, "Win not told")
        self.assertIn(MessageToPlayer.Lose, self.j2.messages, "Lose not told")

    def assertGot(self, player: MockPlayer, message: MessageToPlayer, extra=None, msg: str = "message not received"):
        self.assertIn(message, [t for (t, _) in player.messages], msg)
        if extra:
            matching = [m for (m, e) in player.messages if e == extra and m == message]
            self.assertTrue(matching, f"No message {message} with extra {extra}")
