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

import de.steamwar.core.Core;
import de.steamwar.providers.BauServerInfo;
import de.steamwar.sql.AuditLog;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class SWInventory implements Listener {

    final Player player;
    final Map<Integer, Consumer<InventoryClickEvent>> callbacks = new HashMap<>();
    final Inventory inventory;
    String title;
    boolean open = false;

    public SWInventory(Player p, int size, String t) {
        player = p;
        inventory = Bukkit.createInventory(p, size, t);
    }

    public SWInventory(Player p, int size, String t, Map<Integer, SWItem> items) {
        this(p, size, t);
        items.forEach(this::setItem);
        open();
    }

    public SWInventory(Player p, Supplier<Inventory> inventoryConstructor) {
        player = p;
        try {
            inventory = inventoryConstructor.get();
        } catch (Exception e) {
            throw new SecurityException("Could not construct inventory", e);
        }
    }

    public void addCloseCallback(InvCallback c) {
        callbacks.put(-1, inventoryClickEvent -> c.clicked(null));
    }

    public void addCloseRunnable(Runnable c) {
        callbacks.put(-1, inventoryClickEvent -> c.run());
    }

    public void setItem(int pos, ItemStack itemStack, InvCallback c) {
        inventory.setItem(pos, itemStack);
        if (c != null) {
            callbacks.put(pos, inventoryClickEvent -> c.clicked(inventoryClickEvent.getClick()));
        } else {
            callbacks.remove(pos);
        }
    }

    public boolean hasItem(int pos) {
        return inventory.getItem(pos) != null;
    }

    public void setItem(int pos, SWItem item) {
        setItem(pos, item.getItemStack(), item.getCallback());
    }

    public void setItem(int pos, Material m, String name, InvCallback c) {
        setItem(pos, m, name, new ArrayList<>(), false, c);
    }

    public void setItem(int pos, Material m, byte meta, String name, InvCallback c) {
        setItem(pos, m, meta, name, new ArrayList<>(), false, c);
    }

    public void setItem(int pos, Material m, String name, List<String> lore, boolean e, InvCallback c) {
        setItem(pos, m, (byte) 0, name, lore, e, c);
    }

    public void setItem(int pos, Material m, byte meta, String name, List<String> lore, boolean e, InvCallback c) {
        SWItem item = new SWItem(m, meta, name, lore, e, c);
        setItem(pos, item);
    }

    public void setCallback(int pos, InvCallback c) {
        callbacks.put(pos, inventoryClickEvent -> c.clicked(inventoryClickEvent == null ? null : inventoryClickEvent.getClick()));
    }

    public void setItemEvent(int pos, ItemStack itemStack, Consumer<InventoryClickEvent> c) {
        inventory.setItem(pos, itemStack);
        callbacks.put(pos, c);
    }

    public void setItemEvent(int pos, Material m, String name, Consumer<InventoryClickEvent> c) {
        setItemEvent(pos, m, name, new ArrayList<>(), false, c);
    }

    public void setItemEvent(int pos, Material m, byte meta, String name, Consumer<InventoryClickEvent> c) {
        setItemEvent(pos, m, meta, name, new ArrayList<>(), false, c);
    }

    public void setItemEvent(int pos, Material m, String name, List<String> lore, boolean e, Consumer<InventoryClickEvent> c) {
        setItemEvent(pos, m, (byte) 0, name, lore, e, c);
    }

    public void setItemEvent(int pos, Material m, byte meta, String name, List<String> lore, boolean e, Consumer<InventoryClickEvent> c) {
        SWItem item = new SWItem(m, meta, name, lore, e, click -> {
        });
        setItem(pos, item);
        setEventCallback(pos, c);
    }

    public void setEventCallback(int pos, Consumer<InventoryClickEvent> c) {
        callbacks.put(pos, c);
    }

    public void open() {
        InventoryView view = player.openInventory(inventory);
        title = view.getTitle();
        Core.getInstance().getLogger().info("[SWINV] Opened " + title + " for " + player.getName());
        if (!open) {
            Bukkit.getPluginManager().registerEvents(this, Core.getInstance());
            open = true;
        }
        AuditLog.createGuiOpen(Core.getServerName(), BauServerInfo.getOwnerUser(), SteamwarUser.get(player.getUniqueId()), title);
    }

    @EventHandler
    public void onInventoryClick(InventoryClickEvent e) {
        if (!player.equals(e.getWhoClicked())) return;

        if (callbacks.containsKey(e.getRawSlot()) && callbacks.get(e.getRawSlot()) != null) {
            e.setCancelled(true);
            Core.getInstance().getLogger().info("[SWINV] " + e.getWhoClicked().getName() + " " + e.getClick().name() + " clicked " + e.getRawSlot() + " on " + (e.getCurrentItem() != null ? e.getCurrentItem().getItemMeta().getDisplayName() : "[EMPTY]") + " in " + e.getView().getTitle());
            callbacks.get(e.getRawSlot()).accept(e);
            AuditLog.createGuiClick(Core.getServerName(), BauServerInfo.getOwnerUser(), SteamwarUser.get(player.getUniqueId()), e.getView().getTitle(), e.getClick().name(), e.getRawSlot(), (e.getCurrentItem() != null ? e.getCurrentItem().getItemMeta().getDisplayName() : "[EMPTY]"));
        }
    }

    @EventHandler
    public void onInventoryClose(InventoryCloseEvent e) {
        if (!player.equals(e.getPlayer())) return;

        InventoryClickEvent.getHandlerList().unregister(this);
        InventoryCloseEvent.getHandlerList().unregister(this);
        Core.getInstance().getLogger().info("[SWINV] " + player.getName() + " closed " + title);
        if (callbacks.containsKey(-1)) {
            callbacks.get(-1).accept(null);
        }
        open = false;
        AuditLog.createGuiClose(Core.getServerName(), BauServerInfo.getOwnerUser(), SteamwarUser.get(player.getUniqueId()), title);
    }
}
