package eu.jonahbauer.wizard.core.machine.states.round;

import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.MessageQueue;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.model.Configurations;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.*;

public class DeterminingTrumpTest {
    private final UUID[] players = new UUID[] {
            new UUID(0, 0),
            new UUID(0, 1),
            new UUID(0, 2),
            new UUID(0, 3)
    };

    @SneakyThrows
    @SuppressWarnings("SameParameterValue")
    private Game performTest(GameConfiguration configuration, int round, Map<UUID, List<Card>> hands, Card trumpCard, MessageQueue queue) {
        Game game = spy(new Game(configuration, queue));
        doFinish().when(game).transition(any(Predicting.class));
        queue.setGame(game);

        var playerList = List.of(players);

        GameData data = GameData.EMPTY.with(
                entry(PLAYERS, playerList),
                entry(ROUND, round),
                entry(SCORE, Map.of()),
                entry(TRUMP_CARD, trumpCard),
                entry(HANDS, hands)
        );

        game.resume(new DeterminingTrump(data));
        game.await();

        verify(game).transition(any(Finished.class));
        return game;
    }

    @Test
    public void run_Simple() {
        Map<UUID, List<Card>> hands = Map.of(
                players[0], List.of(Card.RED_1),
                players[1], List.of(Card.CLOUD),
                players[2], List.of(Card.GREEN_1),
                players[3], List.of(Card.BLUE_1)
        );

        // play cards in given order
        MessageQueue queue = new MessageQueue();

        Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.YELLOW_1, queue);

        // validate messages
        InOrder order = inOrder(game);
        order.verify(game).notify(any(StateMessage.class)); // determining trump
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.YELLOW_1 && trump.getSuit() == Card.Suit.YELLOW));
        order.verify(game).transition(any(Predicting.class)); // round is finished
        order.verify(game).notify(any(StateMessage.class)); // finish
        order.verify(game, never()).notify(any());
        order.verify(game, never()).notify(any(), any());
    }

    @Test
    public void run_Simple2() {
        Map<UUID, List<Card>> hands = Map.of(
                players[0], List.of(Card.RED_1),
                players[1], List.of(Card.CLOUD),
                players[2], List.of(Card.GREEN_1),
                players[3], List.of(Card.BLUE_1)
        );

        // play cards in given order
        MessageQueue queue = new MessageQueue();

        Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_JESTER, queue);

        // validate messages
        InOrder order = inOrder(game);
        order.verify(game).notify(any(StateMessage.class)); // determining trump
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.GREEN_JESTER && trump.getSuit() == Card.Suit.NONE));
        order.verify(game).transition(any(Predicting.class));
        order.verify(game).notify(any(StateMessage.class)); // finish
        order.verify(game, never()).notify(any());
        order.verify(game, never()).notify(any(), any());
    }

    @Test
    public void run_WithUserInput() {
        Map<UUID, List<Card>> hands = Map.of(
                players[0], List.of(Card.RED_1),
                players[1], List.of(Card.CLOUD),
                players[2], List.of(Card.GREEN_1),
                players[3], List.of(Card.BLUE_1)
        );

        // play cards in given order
        MessageQueue queue = new MessageQueue()
                .addPickTrump(players[0], Card.Suit.GREEN);

        Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.BLUE_WIZARD, queue);

        // validate messages
        InOrder order = inOrder(game);
        order.verify(game).notify(any(StateMessage.class)); // determining trump
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.BLUE_WIZARD && trump.getSuit() == null));
        order.verify(game).notify(any(UserInputMessage.class)); // user input request
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.BLUE_WIZARD && trump.getSuit() == Card.Suit.GREEN));
        order.verify(game).transition(any(Predicting.class));
        order.verify(game).notify(any(StateMessage.class)); // finish
        order.verify(game, never()).notify(any());
        order.verify(game, never()).notify(any(), any());
    }

    @Test
    public void run_WithWerewolf() {
        Map<UUID, List<Card>> hands = Map.of(
                players[0], List.of(Card.RED_1),
                players[1], List.of(Card.CLOUD),
                players[2], List.of(Card.GREEN_1),
                players[3], List.of(Card.WEREWOLF)
        );

        // play cards in given order
        MessageQueue queue = new MessageQueue()
                .addPickTrump(players[3], Card.Suit.YELLOW);

        Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_1, queue);

        // validate messages
        InOrder order = inOrder(game);
        order.verify(game).notify(any(StateMessage.class)); // determining trump
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.GREEN_1 && trump.getSuit() == null));
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.WEREWOLF && trump.getSuit() == null));
        order.verify(game).notify(any(UserInputMessage.class)); // user input request
        order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.WEREWOLF && trump.getSuit() == Card.Suit.YELLOW));
        order.verify(game).notify(eq(players[3]), any(HandMessage.class)); // swap trump card and werewolf
        order.verify(game).transition(any(Predicting.class));
        order.verify(game).notify(any(StateMessage.class)); // finish
        order.verify(game, never()).notify(any());
        order.verify(game, never()).notify(any(), any());
    }
}
