/*
 *   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.persistent;

import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.name.Names;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.proxy.plugin.PluginClassLoader;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import com.velocitypowered.proxy.plugin.loader.java.JavaPluginLoader;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;

import java.io.IOException;
import java.nio.file.Path;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

@Plugin(
        id = "persistentvelocitycore",
        name = "PersistentVelocityCore"
)
public class Persistent {

    private static final Reflection.Method<VelocityPluginManager> registerPlugin = new Reflection.Method<>(VelocityPluginManager.class, "registerPlugin", PluginContainer.class);

    @Getter
    private static Persistent instance;

    @Getter
    private final ProxyServer proxy;
    @Getter
    private final Logger logger;
    private final Path directory;

    private boolean restartQueued = false;

    @Inject
    public Persistent(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory) {
        instance = this;
        this.proxy = proxy;
        this.logger = logger;
        this.directory = dataDirectory;
    }

    @Subscribe
    public void onEnable(ProxyInitializeEvent event) {
        proxy.getScheduler().buildTask(instance, () -> {
            if (!restartQueued) return;
            if (!proxy.getAllPlayers().isEmpty()) return;
            proxy.shutdown();
        }).repeat(10, TimeUnit.SECONDS).schedule();

        proxy.getCommandManager().register(
                new BrigadierCommand(
                        BrigadierCommand.literalArgumentBuilder("softreload")
                                .requires(commandSource -> commandSource.hasPermission("bungeecore.softreload"))
                                .executes(commandContext -> softreload())
                                .build()
                )
        );
        proxy.getCommandManager().register(
                new BrigadierCommand(
                        BrigadierCommand.literalArgumentBuilder("queuerestart")
                                .requires(commandSource -> commandSource.hasPermission("bungeecore.softreload"))
                                .executes(this::queueRestart)
                                .build()
                )
        );
    }

    @Subscribe
    public void onDisable(ProxyShutdownEvent event) {
        Subserver.shutdown();
    }

    public int softreload() {
        restartQueued = false;
        PluginContainer container = null;
        ReloadablePlugin plugin = null;
        try {
            container = proxy.getPluginManager().getPlugin("velocitycore").orElseThrow();
            plugin = (ReloadablePlugin) container.getInstance().orElseThrow();
        } catch (NoSuchElementException e) {
            e.printStackTrace();
            logger.log(Level.WARNING, "Could not find loaded VelocityCore, continuing without unloading.");
        }

        PluginContainer newContainer;
        try {
            newContainer = prepareLoad();
        } catch (Exception e) {
            e.printStackTrace();
            logger.log(Level.SEVERE, "Could not instantiate new VelocityCore, aborting softreload.", e);
            return Command.SINGLE_SUCCESS;
        }

        broadcast("§eNetwork update is starting§8.");
        try {
            if (container != null && plugin != null) {
                System.out.println("Proxy Shutdown Event");
                plugin.onProxyShutdown(new ProxyShutdownEvent());
                System.out.println("Unloading of Container");
                unload(container, plugin);
            }

            System.out.println("Loading of new Container");
            registerPlugin.invoke((VelocityPluginManager) proxy.getPluginManager(), newContainer);
            System.out.println("Proxy Initialization Event");
            ((ReloadablePlugin) newContainer.getInstance().orElseThrow()).onProxyInitialization(new ProxyInitializeEvent());
            System.out.println("Proxy Initialization Event finished");
        } catch (Throwable t) {
            t.printStackTrace();
            logger.log(Level.SEVERE, "Error during softreload", t);
            broadcast("§cNetwork update failed§8, §cexpect network restart soon§8.");
            return Command.SINGLE_SUCCESS;
        }

        broadcast("§eNetwork update complete§8.");
        return Command.SINGLE_SUCCESS;
    }

    private void broadcast(String message) {
        Component component = LegacyComponentSerializer.legacySection().deserialize("§eSteam§8War» " + message);
        proxy.getAllPlayers().forEach(player -> player.sendMessage(component));
        proxy.getConsoleCommandSource().sendMessage(component);
    }

    private PluginContainer prepareLoad() throws Exception {
        Path plugins = directory.getParent();
        JavaPluginLoader loader = new JavaPluginLoader(proxy, plugins);
        PluginDescription description = loader.createPluginFromCandidate(loader.loadCandidate(plugins.resolve("VelocityCore.jar")));
        PluginContainer container = new VelocityPluginContainer(description);

        AbstractModule commonModule = new AbstractModule() {
            @Override
            protected void configure() {
                this.bind(ProxyServer.class).toInstance(proxy);
                this.bind(PluginManager.class).toInstance(proxy.getPluginManager());
                this.bind(EventManager.class).toInstance(proxy.getEventManager());
                this.bind(CommandManager.class).toInstance(proxy.getCommandManager());
                this.bind(PluginContainer.class).annotatedWith(Names.named(container.getDescription().getId())).toInstance(container);
            }
        };

        Module module = loader.createModule(container);
        loader.createPlugin(container, module, commonModule);

        return container;
    }

    private void unload(PluginContainer container, Object plugin) throws InterruptedException, IOException {
        PluginClassLoader classLoader = ((PluginClassLoader) plugin.getClass().getClassLoader());

        CommandManager commandManager = proxy.getCommandManager();
        for (String alias : commandManager.getAliases()) {
            CommandMeta meta = commandManager.getCommandMeta(alias);
            if (meta != null && meta.getPlugin() == plugin) {
                commandManager.unregister(meta);
            }
        }

        proxy.getEventManager().unregisterListeners(plugin);
        proxy.getScheduler().tasksByPlugin(plugin).forEach(ScheduledTask::cancel);

        container.getExecutorService().shutdown();
        if (!container.getExecutorService().awaitTermination(100, TimeUnit.MILLISECONDS)) {
            logger.log(Level.WARNING, "ExecutorService termination took longer than 100ms, continuing.");
        }

        for (Thread thread : Thread.getAllStackTraces().keySet()) {
            if (thread.getClass().getClassLoader() != classLoader) continue;

            thread.interrupt();
            thread.join(100);

            if (thread.isAlive()) {
                logger.log(Level.WARNING, "Could not stop thread %s of plugin %s. Still running".formatted(thread.getName(), container.getDescription().getId()));
            }
        }

        //TODO close all log handlers
        /*for (Handler handler : plugin.getLogger().getHandlers()) {
            handler.close();
        }*/


        //Clear resource bundle cache
        ResourceBundle.clearCache(classLoader);
        classLoader.close();
    }

    public int queueRestart(CommandContext<CommandSource> context) {
        if (restartQueued) {
            restartQueued = false;
            context.getSource().sendPlainMessage("§eRestart dequeued§8.");
        } else {
            restartQueued = true;
            context.getSource().sendPlainMessage("§eRestart queued§8.");
        }
        return Command.SINGLE_SUCCESS;
    }
}
