package me.km;

import me.hammerle.snuviscript.code.ISnuviLogger;
import me.hammerle.snuviscript.config.SnuviConfig;
import me.kcm.Hooks;
import me.km.databank.DataBank;
import me.km.events.CustomEventCaller;
import me.km.permissions.ModCommandManager;
import me.km.permissions.Permissions;
import me.km.playerbank.DummyPlayerBank;
import me.km.playerbank.IPlayerBank;
import me.km.playerbank.PlayerBank;
import me.km.playerbank.PlayerManager;
import me.km.plots.ProtectionEvents;
import me.km.plots.WorldPlotMap;
import me.km.scheduler.SnuviScheduler;
import me.km.snuviscript.SnuviLogger;
import me.km.utils.ReflectionUtils;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import me.km.snuviscript.CommandScript;
import me.km.snuviscript.MinecraftFunctions;
import me.km.snuviscript.ScriptEvents;
import me.km.snuviscript.Scripts;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;

@OnlyIn(Dist.DEDICATED_SERVER)
public class Server {
    private static SnuviLogger logger;
    private static ModCommandManager commands;
    private static DedicatedServer server;
    public static SnuviScheduler scheduler;
    private static DataBank databank;
    private static Scripts scripts;
    public static ScriptEvents scriptEvents;
    private static WorldPlotMap plotMap;

    private static volatile boolean isRunning = true;
    private static ServerSocket votifierSocket = null;
    private static Thread votifierAccept = null;
    private static PrivateKey privateKey = null;

    public static PrivateKey getPrivateKey(String base64) {
        try {
            PKCS8EncodedKeySpec keySpec =
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64.getBytes()));;
            return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(byte[] data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(data));
    }

    private static void startVotifier(String pKey) {
        privateKey = getPrivateKey(pKey);
        if(privateKey == null) {
            return;
        }

        try {
            votifierSocket = new ServerSocket(12345);
            votifierAccept = new Thread(() -> {
                while(isRunning) {
                    try {
                        Socket client = votifierSocket.accept();
                        InputStream in = client.getInputStream();
                        byte[] data = new byte[256];
                        for(int i = 0; i < 256; i++) {
                            data[i] = (byte) in.read();
                        }
                        String[] s = decrypt(data, privateKey).split("\n");
                        scheduler.scheduleTask(() -> {
                            scriptEvents.onVote(s);
                        });
                    } catch(Exception ex) {
                        if(!votifierSocket.isClosed()) {
                            ex.printStackTrace();
                        }
                    }
                }
            });
            votifierAccept.start();
            logger.print("votifier started");
        } catch(Exception ex) {
            logger.print("------------------------------------------------");
            logger.print("cannot start votifier socket");
            logger.print("------------------------------------------------");
            ex.printStackTrace();
        }
    }

    public static void onStart(DedicatedServer serverIn) {
        server = serverIn;
        logger = new SnuviLogger();

        SnuviConfig conf = new SnuviConfig("", "config");
        if(conf.exists()) {
            conf.load(null);
        }

        boolean debug = conf.getBoolean(null, "debug", false);

        // permissions
        Permissions perms = new Permissions(debug);
        logger.setPlayerLoggingData(server, perms);
        if(debug) {
            logger.print("------------------------------------------------");
            logger.print("Starting server in debug mode");
            logger.print("------------------------------------------------");
        }

        // scheduler
        scheduler = new SnuviScheduler(logger);

        // scripts
        scripts = new Scripts(logger, scheduler);
        scriptEvents = new ScriptEvents(scripts, server);
        Hooks.setBlockHarvest(scriptEvents);
        Hooks.setCraft(scriptEvents);
        Hooks.setContainerClick(scriptEvents);
        MinecraftForge.EVENT_BUS.register(scriptEvents);

        // command manager
        commands = new ModCommandManager(perms, scriptEvents, scripts, scheduler);
        ReflectionUtils.setCommandManager(server, commands);
        commands.registerCommand(new CommandScript(scripts));

        // data base connection
        databank = new DataBank(logger, conf.getString(null, "user", "root"),
                conf.getString(null, "password", ""));
        if(!databank.openDataBankConnection()) {
            logger.print("------------------------------------------------");
            logger.print("Starting server with dummy databank");
            logger.print("------------------------------------------------");
        }
        databank.startReconnecting(scheduler);

        // protections
        plotMap = new WorldPlotMap();
        scheduler.scheduleTask(() -> plotMap.read(server, logger));
        long time = 20 * 60 * 30; // all 30 minutes
        scheduler.scheduleRepeatingTask(() -> plotMap.save(), time, time);

        // block protections / player manager
        IPlayerBank playerBank;
        if(databank.isDummyDatabank()) {
            playerBank = new DummyPlayerBank();
        } else {
            playerBank = new PlayerBank(databank);
        }
        MinecraftForge.EVENT_BUS.register(new ProtectionEvents(plotMap, perms));

        PlayerManager playerManager = new PlayerManager(logger, playerBank);
        MinecraftForge.EVENT_BUS.register(playerManager);
        CustomEventCaller customEventCaller = new CustomEventCaller(server, scheduler);
        scripts.setCustomEventCaller(customEventCaller);
        MinecraftForge.EVENT_BUS.register(customEventCaller);

        // scripts
        MinecraftFunctions.registerFunctions(scripts.getScriptManager(), scripts, perms, scheduler,
                server, playerBank, customEventCaller, databank, plotMap, commands);

        scheduler.scheduleTask(() -> scripts.startScript(null, "startscript"));
        startVotifier(conf.getString(null, "pkey"));
    }

    public static void onStop() {
        scripts.startScript("endscript");
        scheduler.stop();
        databank.closeDataBankConnection();

        isRunning = false;
        try {
            if(votifierSocket != null) {
                votifierSocket.close();
                votifierAccept.join(1000);
            }
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    public static ISnuviLogger getLogger() {
        return logger;
    }
}
