package eu.jonahbauer.wizard.client.libgdx.state;

import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
import eu.jonahbauer.wizard.client.libgdx.util.SavedData;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import eu.jonahbauer.wizard.common.messages.server.*;
import eu.jonahbauer.wizard.common.model.Configuration;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.UUID;

@Log4j2
@Getter
@RequiredArgsConstructor
public final class AwaitingJoinSession extends Awaiting {

    private final @Nullable UUID session;
    private final @NotNull String sessionName;
    private final @NotNull Configuration configuration;
    private final @NotNull Source source;

    @Override
    public Optional<ClientState> onEnter(Client client) {
        log.info("Waiting for acknowledgment...");
        if (!client.isError()) showScreen(client);
        return super.onEnter(client);
    }

    @Override
    public Optional<ClientState> onErrorDismissed(Client client) {
        showScreen(client);
        return Optional.empty();
    }

    @Override
    public Optional<ClientState> onMessage(Client client, ServerMessage message) {
        if (message instanceof SessionJoinedMessage joined) {
            var session = joined.getSession();
            if (this.session != null && !this.session.equals(session)) {
                return unexpectedMessage(client, message);
            } else {
                var players = joined.getPlayers();
                var player = joined.getPlayer();
                var secret = joined.getSecret();

                log.info("There are {} players in this session.", players.size());
                log.info("Your uuid is {}.", player);
                log.info("Your secret is {}.", secret);

                client.getGame().storage.credentials = new SavedData.SessionCredentials(session, player, secret);

                if (source == Source.REJOIN) {
                    var playerMap = new LinkedHashMap<UUID, String>();
                    players.forEach(p -> playerMap.put(p.getUuid(), p.getName()));
                    return Optional.of(new AwaitingGameLog(
                            player,
                            session,
                            sessionName,
                            configuration,
                            playerMap
                    ));
                } else {
                    return Optional.of(new Session(
                            session, sessionName, configuration,
                            players,
                            player
                    ));
                }
            }
        } else if (message instanceof NackMessage nack) {
            switch (nack.getCode()) {
                case NackMessage.GAME_ALREADY_STARTED -> log.error("Game has already started.");
                case NackMessage.SESSION_FULL -> log.error("The session is full.");
                case NackMessage.SESSION_NOT_FOUND -> log.error("Session not found.");
                case NackMessage.PLAYER_NAME_TAKEN -> log.error("Player name already taken.");
                case NackMessage.PLAYER_NAME_NOT_ALLOWED -> log.error("Player name not allowed.");
                case NackMessage.SESSION_NAME_TAKEN -> log.error("Session name already taken.");
                case NackMessage.SESSION_NAME_NOT_ALLOWED -> log.error("Session name not allowed.");
                default -> log.error("Nack {}: {}", nack.getCode(), nack.getMessage());
            }
            showErrorScreen(client, nack.getMessage());
            return Optional.of(new AwaitingJoinLobby());
        } else if (message instanceof SessionModifiedMessage || message instanceof SessionRemovedMessage) {
            // drop
            log.debug("Dropped message {}.", message);
            return Optional.empty();
        } else {
            return unexpectedMessage(client, message);
        }
    }

    private void showScreen(Client client) {
        client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_session"));
    }

    public enum Source {
        JOIN, CREATE, REJOIN
    }
}
