/*
 *   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.bausystem.features.script;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.script.lua.CommandRegister;
import de.steamwar.bausystem.features.script.lua.SteamWarGlobalLuaPlugin;
import de.steamwar.bausystem.features.script.lua.SteamWarPlatform;
import de.steamwar.core.SWPlayer;
import de.steamwar.sql.Script;
import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.UtilityClass;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.luaj.vm2.*;
import org.luaj.vm2.lib.OneArgFunction;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@UtilityClass
public class ScriptRunner {

    // Script Table
    // User
    // Key   -> bau-script-<BUCH NAME>
    // Value -> <LUA Script>

    public class ScriptData implements SWPlayer.Component {
        private Map<SteamWarGlobalLuaPlugin.EventType, List<LuaFunction>> events = new EnumMap<>(SteamWarGlobalLuaPlugin.EventType.class);
        private Map<Hotkey, List<LuaFunction>> hotkeys = new HashMap<>();
        private Map<String, CommandRegister> commands = new HashMap<>();

        @Getter
        private Set<String> calledCommands = new HashSet<>();

        @Getter
        @Setter
        private Long lastF = Long.MAX_VALUE;

        @Getter
        @Setter
        private boolean ignore;
    }

    public Set<String> getCommandsOfPlayer(Player player) {
        return SWPlayer.of(player).getComponentOrDefault(ScriptData.class, ScriptData::new).commands.keySet();
    }

    public static void runScript(String script, Player player) {
        Globals globals = SteamWarPlatform.createClickGlobals(player);
        catchScript("SCRIPT_ERROR_CLICK", player, () -> globals.load(script).call());
    }

    public static void updateGlobalScript(Player player) {
        SteamwarUser user = SteamwarUser.get(player.getUniqueId());
        ScriptRunner.createGlobalScript(Script.list(user).stream().map(Script::getCode).collect(Collectors.toList()), player);
    }

    public static void createGlobalScript(List<String> scripts, Player player) {
        remove(player);
        SWPlayer swPlayer = SWPlayer.of(player);
        ScriptData scriptData = swPlayer.getComponentOrDefault(ScriptData.class, ScriptData::new);
        Globals globals = SteamWarPlatform.createGlobalGlobals(player,
                (s, luaFunction) -> scriptData.events.computeIfAbsent(s, s1 -> new ArrayList<>()).add(luaFunction),
                (s, luaFunction) -> scriptData.hotkeys.computeIfAbsent(Hotkey.fromString(s), s1 -> new ArrayList<>()).add(luaFunction),
                commandRegister -> scriptData.commands.put(commandRegister.getName(), commandRegister));

        for (String script : scripts) {
            catchScript("SCRIPT_ERROR_GLOBAL", player, () -> globals.load(script).call());
        }
    }

    public static void remove(Player player) {
        SWPlayer.of(player).removeComponent(ScriptData.class);
    }

    public static void callEvent(Player player, SteamWarGlobalLuaPlugin.EventType event, LuaValue eventValue, Event wrappedEvent) {
        List<LuaFunction> luaFunctions = SWPlayer.of(player).getComponent(ScriptData.class)
                .map(scriptData -> scriptData.events)
                .orElse(Collections.emptyMap())
                .getOrDefault(event, Collections.emptyList());
        if (luaFunctions.isEmpty()) {
            if (event == SteamWarGlobalLuaPlugin.EventType.DoubleSwap) {
                player.performCommand("gui");
            }
            return;
        }

        if (eventValue == LuaValue.NIL) {
            eventValue = LuaValue.tableOf();
        }

        AtomicBoolean cancelled = new AtomicBoolean(false);

        if (wrappedEvent instanceof Cancellable) {
            eventValue.set("setCancelled", new OneArgFunction() {
                @Override
                public LuaValue call(LuaValue arg) {
                    cancelled.set(arg.checkboolean());
                    return valueOf(cancelled.get());
                }
            });
        } else {
            eventValue.set("setCancelled", new OneArgFunction() {
                @Override
                public LuaValue call(LuaValue arg) {
                    throw new LuaError("Event is not cancellable");
                }
            });
        }

        final LuaValue finalEventValue = eventValue;

        for (LuaFunction luaFunction : luaFunctions) {
            catchScript("SCRIPT_ERROR_GLOBAL", player, () -> luaFunction.call(finalEventValue));
        }

        if (wrappedEvent instanceof Cancellable) {
            ((Cancellable) wrappedEvent).setCancelled(cancelled.get());
        }
    }

    public static boolean callCommand(Player player, String command, String[] argsArray) {
        CommandRegister commandRegister = SWPlayer.of(player).getComponent(ScriptData.class)
                .map(scriptData -> scriptData.commands)
                .orElse(Collections.emptyMap())
                .get(command);
        if (commandRegister == null) {
            return false;
        }

        LuaValue[] values = new LuaValue[argsArray.length - 1];
        for (int i = 1; i < argsArray.length; i++) {
            values[i - 1] = LuaValue.valueOf(argsArray[i]);
        }

        LuaTable args = LuaValue.listOf(values);
        args.set("alias", command);

        args.set("hasShortFlag", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue arg) {
                String s = arg.checkjstring();
                if (!s.matches("-?[a-zA-Z]")) {
                    throw new LuaError("Short Flag must be one character");
                }
                String flag = s.charAt(s.length() - 1) + "";
                boolean hasFlag = false;
                for (String arg1 : argsArray) {
                    if (arg1.startsWith("-") && arg1.contains(flag)) {
                        hasFlag = true;
                        break;
                    }
                }
                return valueOf(hasFlag);
            }
        });

        args.set("removeShortFlag", new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue arg) {
                String s = arg.checkjstring();
                if (!s.matches("-?[a-zA-Z]")) {
                    throw new LuaError("Short Flag must be one character");
                }
                String flag = s.charAt(s.length() - 1) + "";
                boolean hasFlag = false;
                for (int i = 0; i < argsArray.length; i++) {
                    String arg1 = argsArray[i];
                    if (arg1.startsWith("-") && arg1.contains(flag)) {
                        String newArg = arg1.replace(flag, "");
                        if (newArg.equals("-")) {
                            args.remove(i);
                        } else {
                            args.set(i, newArg);
                        }
                        hasFlag = true;
                        break;
                    }
                }
                return valueOf(hasFlag);
            }
        });

        catchScript("SCRIPT_ERROR_GLOBAL", player, () -> commandRegister.getFunction().call(args));
        return true;
    }

    public static void callHotkey(int mods, int key, Player player, boolean pressed) {
        Hotkey hotkey = Hotkey.fromChar(key, mods);
        List<LuaFunction> hotkeys = SWPlayer.of(player).getComponent(ScriptData.class)
                .map(scriptData -> scriptData.hotkeys)
                .orElse(Collections.emptyMap())
                .getOrDefault(hotkey, Collections.emptyList());
        catchScript("SCRIPT_ERROR_GLOBAL", player, () -> hotkeys.forEach(luaFunction -> luaFunction.call(LuaValue.valueOf(pressed))));
    }

    public static void catchScript(String errorMsg, Player player, Runnable run) {
        try {
            run.run();
        } catch (Exception e) {
            String[] sp = e.getMessage().split(":");
            int index = 0;
            for (int i = sp.length - 1; i >= 0; i--) {
                String[] ss = sp[i].split(" ");
                boolean num = ss[0].chars().mapToObj(Character::isDigit).allMatch(b -> b);
                if (num && !ss[0].isEmpty()) {
                    index = i;
                    break;
                }
            }
            BauSystem.MESSAGE.send(errorMsg, player, String.join(":", Arrays.copyOfRange(sp, index, sp.length)));
        }
    }
}
