/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.velocitycore.commands;

import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.util.DiscordAlert;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;

import java.awt.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.logging.Level;

@Linked
public class CheckCommand extends SWCommand {
    private static final Map<UUID, CheckSession> currentCheckers = new HashMap<>();
    private static final Map<Integer, CheckSession> currentSchems = new HashMap<>();

    public static boolean isChecking(Player player) {
        return currentCheckers.containsKey(player.getUniqueId());
    }

    public static SchematicNode getCheckingSchem(Player player) {
        return currentCheckers.get(player.getUniqueId()).schematic;
    }

    public static Message getWaitTime(SchematicNode schematic) {
        long waitedMillis = Timestamp.from(Instant.now()).getTime() - schematic.getLastUpdate().getTime();
        String ce = waitedMillis > 86400000 ? "c" : "e";
        String color = waitedMillis > 14400000 ? ce : "a";
        long hours = waitedMillis / 3600000;
        long minutes = (waitedMillis - hours * 3600000) / 60000;
        return new Message("CHECK_LIST_WAIT", color, hours, (minutes < 10) ? "0" + minutes : minutes);
    }

    public CheckCommand() {
        super("check", UserPerm.CHECK);

        VelocityCore.schedule(() -> sendReminder(Chatter.serverteam())).repeat(10, TimeUnit.MINUTES).schedule();
    }

    public static void sendReminder(Chatter chatter) {
        List<SchematicNode> schematics = getSchemsToCheck();
        if (schematics.size() == currentCheckers.size()) return;

        chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), schematics.size() - currentCheckers.size());
    }

    @Register(value = "list", description = "CHECK_HELP_LIST")
    public void list(Chatter sender) {
        List<SchematicNode> schematicList = getSchemsToCheck();

        sender.system("CHECK_LIST_HEADER", schematicList.size());

        for (SchematicNode schematic : schematicList) {
            GameModeConfig<String, String> gameModeConfig = ArenaMode.getBySchemType(schematic.getSchemtype());
            if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults();
            CheckSession current = currentSchems.get(schematic.getId());
            ClickEvent clickEvent = null;
            Message hoverMessage = null;
            if (gameModeConfig.Checkers.isEmpty() || gameModeConfig.Checkers.contains(sender.user().getId())) {
                if (current == null) {
                    clickEvent = ClickEvent.runCommand("/check schematic " + schematic.getId());
                    hoverMessage = new Message("CHECK_LIST_TO_CHECK_HOVER");
                } else {
                    clickEvent = ClickEvent.runCommand("/join " + current.checker.user().getUserName());
                    hoverMessage = new Message("CHECK_LIST_CHECKING_HOVER");
                }
            }

            if (current == null) {
                sender.prefixless("CHECK_LIST_TO_CHECK",
                        hoverMessage,
                        clickEvent,
                        getWaitTime(schematic),
                        schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName());
            } else {
                sender.prefixless("CHECK_LIST_CHECKING",
                        hoverMessage,
                        clickEvent,
                        getWaitTime(schematic),
                        schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName(), current.checker.user().getUserName());
            }
        }
    }

    @Register(value = "schematic", noTabComplete = true)
    public void schematic(PlayerChatter sender, String schemID) {
        if (isChecking(sender.getPlayer())) {
            sender.system("CHECK_SCHEMATIC_ALREADY_CHECKING");
            return;
        }

        SchematicNode schem = SchematicNode.getSchematicNode(Integer.parseInt(schemID));
        GameModeConfig<String, String> gameModeConfig = ArenaMode.getBySchemType(schem.getSchemtype());
        if (!gameModeConfig.Checkers.isEmpty() && !gameModeConfig.Checkers.contains(sender.user().getId())) {
            sender.system("CHECK_SCHEMATIC_EVENT");
            return;
        }
        if (!schem.getSchemtype().check()) {
            VelocityCore.getLogger().log(Level.SEVERE, () -> sender.user().getUserName() + " tried to check an uncheckable schematic!");
            return;
        } else if (schem.getOwner() == sender.user().getId() && !sender.user().hasPerm(UserPerm.ADMINISTRATION)) {
            sender.system("CHECK_SCHEMATIC_OWN");
            return;
        }

        int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam();
        // Ignore 795 SteamWar Team
        if (playerTeam != 0 && playerTeam != 795 && SteamwarUser.byId(schem.getOwner()).getTeam() == playerTeam) {
            sender.system("CHECK_SCHEMATIC_OWN_TEAM");
            return;
        }

        CheckSession checkSession = currentSchems.get(schem.getId());
        if (checkSession != null) {
            Optional<ServerConnection> serverConnection = checkSession.checker.getPlayer().getCurrentServer();
            if (serverConnection.isPresent()) {
                sender.getPlayer().createConnectionRequest(serverConnection.get().getServer()).fireAndForget();
                return;
            }
        }
        new CheckSession(sender, schem);
    }

    @Register(value = "cancel", description = "CHECK_HELP_CANCEL")
    @Register("abort")
    public void abortCommand(PlayerChatter sender) {
        abort(sender.getPlayer());
    }

    public static void abort(Player player) {
        if (notChecking(player)) return;

        Chatter.of(player).system("CHECK_ABORT");
        currentCheckers.get(player.getUniqueId()).abort();
    }

    @Register(value = "next", description = "CHECK_HELP_NEXT")
    public void next(PlayerChatter sender) {
        if (notChecking(sender.getPlayer())) return;

        currentCheckers.get(sender.getPlayer().getUniqueId()).next();
    }

    @Register(value = "accept")
    public void accept(PlayerChatter sender) {
        next(sender);
    }

    @Register(value = "decline", description = "CHECK_HELP_DECLINE")
    public void decline(PlayerChatter sender) {
        if (notChecking(sender.getPlayer())) return;

        currentCheckers.get(sender.getPlayer().getUniqueId()).markDeclined();
    }

    @Register(value = "decline", description = "CHECK_HELP_DECLINE")
    public void decline(PlayerChatter sender, String... message) {
        if (notChecking(sender.getPlayer())) return;

        currentCheckers.get(sender.getPlayer().getUniqueId()).decline(String.join(" ", message));
    }

    public static List<SchematicNode> getSchemsToCheck() {
        List<SchematicNode> schematicList = new ArrayList<>();

        for (SchematicType type : SchematicType.values()) {
            if (type.check()) {
                schematicList.addAll(SchematicNode.getAllSchematicsOfType(type));
            }
        }
        return schematicList;
    }

    public static String getChecker(SchematicNode schematic) {
        if (currentSchems.get(schematic.getId()) == null) return null;
        return currentSchems.get(schematic.getId()).checker.user().getUserName();
    }

    private static boolean notChecking(Player player) {
        if (!isChecking(player)) {
            Chatter.of(player).system("CHECK_NOT_CHECKING");
            return true;
        }
        return false;
    }

    private static class CheckSession {
        private final PlayerChatter checker;
        private final SchematicNode schematic;
        private final Timestamp startTime;
        private final ListIterator<String> checkList;
        private String currentQuestion;
        private final List<String> declinedQuestions = new ArrayList<>();

        private CheckSession(PlayerChatter checker, SchematicNode schematic) {
            this.checker = checker;
            this.schematic = schematic;
            this.startTime = Timestamp.from(Instant.now());
            this.checkList = GameModeConfig.getBySchematicType(schematic.getSchemtype()).CheckQuestions.listIterator();

            GameModeConfig<String, String> mode = GameModeConfig.getBySchematicType(schematic.getSchemtype());
            new ServerStarter().test(mode, mode.getRandomMap(), checker.getPlayer()).check(schematic.getId()).callback(subserver -> {
                currentCheckers.put(checker.user().getUUID(), this);
                currentSchems.put(schematic.getId(), this);

                for (CheckedSchematic previous : CheckedSchematic.previousChecks(schematic)) {
                    checker.prefixless("CHECK_SCHEMATIC_PREVIOUS", previous.getEndTime(), SteamwarUser.byId(previous.getValidator()).getUserName(), previous.getDeclineReason());
                }
                next();
            }).start();
        }

        private void next() {
            if (!checkList.hasNext()) {
                if (declinedQuestions.isEmpty()) {
                    accept();
                } else {
                    checker.system("CHECK_DECLINED_QUESTIONS");
                    int i = 1;
                    for (String s : declinedQuestions) {
                        checker.prefixless("CHECK_DECLINED_QUESTION_FORMAT", i++, s);
                    }
                    declinedQuestions.clear();
                    checker.sendMessage(Component
                            .text(checker.parseToPlain("CHECK_ACCEPT"))
                            .color(NamedTextColor.GREEN)
                            .clickEvent(ClickEvent.suggestCommand("/check accept"))
                            .append(Component
                                    .text("   " + checker.parseToPlain("CHECK_DECLINE"))
                                    .color(NamedTextColor.RED)
                                    .clickEvent(ClickEvent.suggestCommand("/check decline "))));
                }
                return;
            }

            currentQuestion = checkList.next();

            checker.prefixless("PLAIN_STRING", currentQuestion);

            checker.sendMessage(Component
                    .text(checker.parseToPlain(checkList.hasNext() ? "CHECK_NEXT" : "CHECK_ACCEPT"))
                    .color(NamedTextColor.GREEN)
                    .clickEvent(ClickEvent.runCommand("/check next"))
                    .append(Component
                            .text("   " + checker.parseToPlain("CHECK_MARK_DECLINE"))
                            .color(NamedTextColor.RED)
                            .clickEvent(ClickEvent.runCommand("/check decline"))));
        }

        private void markDeclined() {
            declinedQuestions.add(currentQuestion);
            next();
        }

        private void accept() {
            // TODO: This Code is only for the WGS and because YoyoNow is not available this can be removed or changed after the WGS!
            if (schematic.getSchemtype().toDB().equals("cwargearseason26")) {
                int userId = schematic.getOwner();
                SteamwarUser user = SteamwarUser.byId(userId);
                int teamId = user.getTeam();

                SchematicNode teamFolder = SchematicNode.getSchematicNodeInNode(172325)
                        .stream()
                        .filter(schematicNode -> schematicNode.getName().startsWith(teamId + "_"))
                        .findFirst()
                        .orElse(null);
                if (teamFolder == null) {
                    internalAccept();
                    return;
                }

                // Copy Schem into team folder of -1 user
                String name = DateTimeFormatter.ofPattern("yyyy.MM.dd_HH:mm:ss").format(schematic.getLastUpdate().toLocalDateTime());
                NodeData data = NodeData.getLatest(schematic);
                SchematicNode node = SchematicNode.createSchematic(-1, name, teamFolder.getNodeId());
                NodeData.saveFromStream(node, data.schemData(false), data.getNodeFormat());

                // Accept the team folder schematic and set other to Normal as well as adding the original owner on the schematic
                node.setSchemtype(GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type);
                NodeMember.createNodeMember(node.getNodeId(), schematic.getOwner());

                // Remove any added players from the schematic in the folder
                for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
                    if (schematicNode.getNodeId() == node.getNodeId()) continue;
                    for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
                        NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
                        nodeMember.delete();
                    }
                }

                // Conclude by setting send in schematic to normal and broadcast
                concludeCheckSession("freigegeben", SchematicType.Normal, () -> {
                    Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
                    owner.withPlayerOrOffline(
                            player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
                            () -> DiscordAlert.send(owner, Color.GREEN, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_ACCEPT", schematic.getName()), true)
                    );
                    notifyTeam(new Message("CHECK_ACCEPTED_TEAM", schematic.getName(), owner.user().getUserName()));

                    return owner.getPlayer() != null;
                });
                return;
            }

            internalAccept();
        }

        private void internalAccept() {
            concludeCheckSession("freigegeben", GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type, () -> {
                Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
                owner.withPlayerOrOffline(
                        player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
                        () -> DiscordAlert.send(owner, Color.GREEN, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_ACCEPT", schematic.getName()), true)
                );
                notifyTeam(new Message("CHECK_ACCEPTED_TEAM", schematic.getName(), owner.user().getUserName()));

                return owner.getPlayer() != null;
            });
        }

        private void decline(String reason) {
            concludeCheckSession(reason, SchematicType.Normal, () -> {
                Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
                owner.withPlayerOrOffline(
                        player -> owner.system("CHECK_DECLINED", schematic.getSchemtype().name(), schematic.getName(), reason),
                        () -> DiscordAlert.send(owner, Color.RED, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_DECLINE", schematic.getName(), reason), false)
                );
                notifyTeam(new Message("CHECK_DECLINED_TEAM", schematic.getName(), owner.user().getUserName(), reason));

                return owner.getPlayer() != null;
            });
        }

        private void notifyTeam(Message message) {
            Chatter.serverteam().system(message);
            DiscordBot.withBot(bot -> bot.getChecklistChannel().system(message));
        }

        private void abort() {
            concludeCheckSession("Prüfvorgang abgebrochen", null, () -> true);
        }

        private void concludeCheckSession(String reason, SchematicType type, BooleanSupplier sendMessageIsOnline) {
            if (SchematicNode.getSchematicNode(schematic.getId()) != null) {
                CheckedSchematic.create(schematic, checker.user().getId(), startTime, Timestamp.from(Instant.now()), reason, sendMessageIsOnline.getAsBoolean());
                if (type != null) {
                    schematic.setSchemtype(type);
                    if (type == SchematicType.Normal) {
                        schematic.setPrepared(false);
                    }
                }
            }

            remove();
            VelocityCore.schedule(() -> {
                Bauserver subserver = Bauserver.get(checker.user().getUUID());
                if (subserver != null) subserver.stop();
            }).schedule();
        }

        private void remove() {
            currentCheckers.remove(checker.user().getUUID());
            currentSchems.remove(schematic.getId());
        }
    }
}
