/* 
    This file is a part of the SteamWar software.
    
    Copyright (C) 2020  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 de.steamwar.command.SWCommand;
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.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

public class CheckCommand extends SWCommand {
    private static final Map<SchematicType, SchematicType> fightTypes = new HashMap<>();
    private static final Map<SchematicType, List<String>> checkQuestions = new HashMap<>();

    private static final Map<UUID, CheckSession> currentCheckers = new HashMap<>();
    private static final Map<Integer, CheckSession> currentSchems = new HashMap<>();

    public static void setCheckQuestions(SchematicType checkType, List<String> checkQuestions) {
        CheckCommand.checkQuestions.put(checkType, checkQuestions);
    }

    public static void addFightType(SchematicType checkType, SchematicType fightType) {
        fightTypes.put(checkType, fightType);
    }

    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) {
            CheckSession current = currentSchems.get(schematic.getId());
            if (current == null) {
                sender.prefixless("CHECK_LIST_TO_CHECK",
                        new Message("CHECK_LIST_TO_CHECK_HOVER"),
                        ClickEvent.runCommand("/check schematic " + schematic.getId()),
                        getWaitTime(schematic),
                        schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName());
            } else {
                sender.prefixless("CHECK_LIST_CHECKING",
                        new Message("CHECK_LIST_CHECKING_HOVER"),
                        ClickEvent.runCommand("/join " + current.checker.user().getUserName()),
                        getWaitTime(schematic),
                        schematic.getSchemtype().getKuerzel(), SteamwarUser.get(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));
        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.system("CHECK_SCHEMATIC_OWN");
            return;
        }

        int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam();
        if (playerTeam != 0 && SteamwarUser.get(schem.getOwner()).getTeam() == playerTeam) {
            sender.system("CHECK_SCHEMATIC_OWN_TEAM");
            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, 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 CheckSession(PlayerChatter checker, SchematicNode schematic){
            this.checker = checker;
            this.schematic = schematic;
            this.startTime = Timestamp.from(Instant.now());
            this.checkList = checkQuestions.get(schematic.getSchemtype()).listIterator();

            ArenaMode mode = ArenaMode.getBySchemType(fightTypes.get(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.get(previous.getValidator()).getUserName(), previous.getDeclineReason());
                next();
            }).start();
        }

        private void next() {
            if(!checkList.hasNext()){
                accept();
                return;
            }

            checker.prefixless("PLAIN_STRING", checkList.next());

            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_DECLINE"))
                            .color(NamedTextColor.RED)
                            .clickEvent(ClickEvent.suggestCommand("/check decline "))));
        }

        private void accept(){
            if(concludeCheckSession("freigegeben", fightTypes.get(schematic.getSchemtype()))) {
                Chatter owner = Chatter.of(SteamwarUser.get(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()));
            }
        }

        private void decline(String reason){
            if(concludeCheckSession(reason, SchematicType.Normal)) {
                Chatter owner = Chatter.of(SteamwarUser.get(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));
            }
        }

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

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

        private boolean concludeCheckSession(String reason, SchematicType type) {
            boolean exists = SchematicNode.getSchematicNode(schematic.getId()) != null;

            if(exists) {
                CheckedSchematic.create(schematic, checker.user().getId(), startTime, Timestamp.from(Instant.now()), reason);
                if(type != null)
                    schematic.setSchemtype(type);
            }

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

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