package net.apunch.blacksmith;

import net.apunch.blacksmith.config.Setting;
import net.apunch.blacksmith.config.Settings;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.util.DataKey;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import regalowl.hyperconomy.HyperAPI;
import regalowl.hyperconomy.HyperConomy;
import regalowl.hyperconomy.bukkit.BukkitConnector;
import regalowl.hyperconomy.inventory.HItemStack;
import regalowl.hyperconomy.tradeobject.TradeObject;

import java.util.ArrayList;
import java.util.Objects;
import java.util.logging.Level;

import static net.apunch.blacksmith.util.Sanitizer.sanitizeItemName;

/**
 * Blacksmith's main class
 */
public class BlacksmithPlugin extends JavaPlugin {

    private static BlacksmithPlugin instance;
    private Settings config;
    private Economy economy;
    private HyperAPI hyperAPI;
    private BukkitConnector bukkitConnector;
    private boolean useHyperAPI = false;

    /**
     * Gets an instance of the Blacksmith plugin
     *
     * @return <p>An instance of the blacksmith plugin</p>
     */
    public static BlacksmithPlugin getInstance() {
        return instance;
    }

    /**
     * Gets settings for the blacksmith plugin
     *
     * @return <p>Settings for the blacksmith plugin</p>
     */
    public Settings getSettings() {
        return config;
    }

    @Override
    public void onDisable() {
        //	config.save();

        getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " disabled.");
    }

    @Override
    public void onEnable() {
        instance = this;

        //Load settings
        config = new Settings(this);
        config.load();
        //Load HyperConomy if available
        setupHyperConomy();

        getLogger().log(Level.INFO, "Setting Up Vault now....");
        boolean canLoad = setupVault();
        if (!canLoad) {
            getLogger().log(Level.SEVERE, "Vault Integration Failed....");
            getServer().getPluginManager().disablePlugin(this);
            return;
        }
        //Register the blacksmith trait with Citizens
        CitizensAPI.getTraitFactory().registerTrait(
                net.citizensnpcs.api.trait.TraitInfo.create(BlacksmithTrait.class).withName("blacksmith"));

        getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " enabled.");
    }

    /**
     * Sets up HyperConomy
     *
     * <p>Setup HyperConomy (Soft-Depend only, so this is completely optional!). HyperConomy uses your favorite
     * Vault-compatible economy system and calculates prices for items based on supply and demand on the fly.
     * This is only used to get the cost of a repair.</p>
     */
    private void setupHyperConomy() {
        if (Bukkit.getPluginManager().getPlugin("HyperConomy") != null) {
            getServer().getLogger().log(Level.INFO, "Found HyperConomy! Using that for calculating prices, " +
                    "base-prices and price-per-durability-point in the Blacksmith config.yml will NOT be used!");
            this.useHyperAPI = true;
            Plugin hyperConomyPlugin = getServer().getPluginManager().getPlugin("HyperConomy");
            bukkitConnector = (BukkitConnector) hyperConomyPlugin;
            HyperConomy hyperConomy = Objects.requireNonNull(bukkitConnector).getHC();
            this.hyperAPI = (HyperAPI) hyperConomy.getAPI();
        }
    }

    /**
     * Sets up Vault for economy
     *
     * @return <p>True if Vault was successfully set up</p>
     */
    private boolean setupVault() {
        // Setup Vault
        RegisteredServiceProvider<Economy> economyProvider = getServer().getServicesManager().getRegistration(
                Economy.class);
        if (economyProvider != null) {
            economy = economyProvider.getProvider();
            return true;
        } else {
            // Disable if no economy plugin was found
            getServer().getLogger().log(Level.SEVERE, "Failed to load an economy plugin. Disabling...");
            return false;
        }
    }

    @Override
    public boolean onCommand(final CommandSender sender, final @NotNull Command command, final @NotNull String label,
                             final String[] args) {
        //Handle the reload command
        config.load();
        sender.sendMessage(ChatColor.GREEN + "Blacksmith config reloaded!");
        return true;
    }

    /**
     * Gets whether the given item is a type of tool
     *
     * @param item <p>The item to check if is tool or not</p>
     * @return <p>True if the given item is a type of tool</p>
     */
    public boolean isTool(ItemStack item) {
        return switch (item.getType()) {
            case WOODEN_PICKAXE, WOODEN_SHOVEL, WOODEN_HOE, WOODEN_SWORD, WOODEN_AXE, STONE_PICKAXE, STONE_SHOVEL,
                    STONE_HOE, STONE_SWORD, STONE_AXE, GOLDEN_PICKAXE, GOLDEN_SHOVEL, GOLDEN_HOE, GOLDEN_SWORD,
                    GOLDEN_AXE, IRON_PICKAXE, IRON_SHOVEL, IRON_HOE, IRON_SWORD, IRON_AXE, DIAMOND_PICKAXE,
                    DIAMOND_SHOVEL, DIAMOND_HOE, DIAMOND_SWORD, DIAMOND_AXE, NETHERITE_SWORD, NETHERITE_SHOVEL,
                    NETHERITE_PICKAXE, NETHERITE_AXE, NETHERITE_HOE, BOW, CROSSBOW, FLINT_AND_STEEL, FISHING_ROD,
                    SHEARS, TRIDENT, SHIELD -> true;
            default -> false;
        };
    }

    /**
     * Gets whether the given item is a type of armor
     *
     * @param item <p>The item to check if is armor or not</p>
     * @return <p>True if the given item is a type of armor</p>
     */
    public static boolean isArmor(ItemStack item) {
        return switch (item.getType()) {
            case LEATHER_HELMET, LEATHER_CHESTPLATE, LEATHER_LEGGINGS, LEATHER_BOOTS, CHAINMAIL_HELMET,
                    CHAINMAIL_CHESTPLATE, CHAINMAIL_LEGGINGS, CHAINMAIL_BOOTS, GOLDEN_HELMET, GOLDEN_CHESTPLATE,
                    GOLDEN_LEGGINGS, GOLDEN_BOOTS, IRON_HELMET, IRON_CHESTPLATE, IRON_LEGGINGS, IRON_BOOTS,
                    DIAMOND_HELMET, DIAMOND_CHESTPLATE, DIAMOND_LEGGINGS, DIAMOND_BOOTS, TURTLE_HELMET, ELYTRA,
                    NETHERITE_HELMET, NETHERITE_CHESTPLATE, NETHERITE_LEGGINGS, NETHERITE_BOOTS -> true;
            default -> false;
        };
    }

    public boolean doesPlayerHaveEnough(Player player) {
        return economy.getBalance(player) - getCost(player.getInventory().getItemInMainHand(), player) >= 0;
    }

    public String formatCost(Player player) {
        double cost = getCost(player.getInventory().getItemInMainHand(), player);
        return economy.format(cost);
    }

    public void withdraw(Player player) {
        economy.withdrawPlayer(player, getCost(player.getInventory().getItemInMainHand(), player));
    }

    /**
     * Gets the durability of the given item
     *
     * @param itemStack <p>The item to get the durability of</p>
     * @return <p>The durability of the item</p>
     */
    public static short getDurability(ItemStack itemStack) {
        Damageable damageable = (Damageable) itemStack.getItemMeta();
        return (short) (itemStack.getType().getMaxDurability() - damageable.getDamage());
    }

    /**
     * Gets the damage done to the given item
     *
     * @param itemStack <p>The damage done to the item</p>
     * @return <p>The damage done to the item</p>
     */
    public static short getDamage(ItemStack itemStack) {
        Damageable damageable = (Damageable) itemStack.getItemMeta();
        return (short) damageable.getDamage();
    }

    private double getCost(ItemStack item, Player player) {
        DataKey root = config.getConfig().getKey("");
        String itemName = sanitizeItemName(item.getType().name());
        double price;
        if (root.keyExists("base-prices." + itemName)) {
            price = root.getDouble("base-prices." + itemName);
        } else {
            price = Setting.BASE_PRICE.asDouble();
        }

        // Adjust price based on durability and enchantments
        if (this.useHyperAPI) {
            return getHyperAPICost(player, item, root, price);
        } else {
            double pricePerDurabilityPoint;
            if (root.keyExists("price-per-durability-point." + itemName)) {
                pricePerDurabilityPoint = root.getDouble("price-per-durability-point." + itemName);
            } else {
                pricePerDurabilityPoint = Setting.PRICE_PER_DURABILITY_POINT.asDouble();
            }
            if (config.getNaturalCost()) {
                //Cost increases with damage
                price += ((double) getDamage(item)) * pricePerDurabilityPoint;
            } else {
                //Cost decreases with damage
                price += ((double) getDurability(item)) * pricePerDurabilityPoint;
            }
        }

        //Add the enchantment modifier for each enchantment on the item
        price += getEnchantmentCost(item, root);
        return price;
    }

    private double getHyperAPICost(Player player, ItemStack item, DataKey root, double price) {
        // If using hyperConomy, price is calculated like so:
        // New Item Price + Enchantments Price (from hyperConomy) / maxDurability = price per durability point
        // Total price would then be base_price + price per durability point * current durability
        double hyperPrice = 0;
        HItemStack hi = hyperAPI.getHyperPlayer(player.getName()).getItemInHand();
        ItemStack item2 = player.getInventory().getItemInMainHand().clone();

        for (TradeObject enchant : hyperAPI.getEnchantmentHyperObjects(hi, player.getName())) {
            hyperPrice = hyperPrice + enchant.getBuyPrice(1);
            Enchantment enchantment = Enchantment.getByKey(
                    NamespacedKey.minecraft(enchant.getEnchantment().getEnchantmentName()));
            item2.removeEnchantment(Objects.requireNonNull(enchantment));
        }

        ArrayList<Material> leathers = new ArrayList<>();
        leathers.add(Material.LEATHER_BOOTS);
        leathers.add(Material.LEATHER_CHESTPLATE);
        leathers.add(Material.LEATHER_HELMET);
        leathers.add(Material.LEATHER_LEGGINGS);

        HItemStack hi3 = null;
        if (leathers.contains(player.getInventory().getItemInMainHand().getType())) {
            hi3 = bukkitConnector.getBukkitCommon().getSerializableItemStack(
                    new ItemStack(player.getInventory().getItemInMainHand().getType()));
        }

        TradeObject to = this.hyperAPI.getHyperObject(hi, "default");
        if (to == null) {
            to = hyperAPI.getHyperObject(hi3, "default");
            if (to == null) {
                HItemStack hi4 = bukkitConnector.getBukkitCommon().getSerializableItemStack(
                        new ItemStack(player.getInventory().getItemInMainHand().getType()));
                to = this.hyperAPI.getHyperObject(hi4, "default");
            }
            hyperPrice = hyperPrice + to.getSellPrice(1);

        } else {
            hyperPrice = to.getSellPrice(1);
        }
        double hyperPricePerDurability = hyperPrice / item.getType().getMaxDurability();
        price += getDurability(item) * hyperPricePerDurability;

        price += getEnchantmentCost(item2, root);

        return price;
    }

    /**
     * Gets the cost resulting from all enchantments on the given item
     *
     * @param item <p>The item to calculate enchantment cost for</p>
     * @param root <p>The data key containing the enchantment-modifiers option</p>
     * @return <p>The resulting enchantment cost</p>
     */
    private double getEnchantmentCost(ItemStack item, DataKey root) {
        double enchantmentModifier = Setting.ENCHANTMENT_MODIFIER.asDouble();
        double price = 0;
        for (Enchantment enchantment : item.getEnchantments().keySet()) {
            String enchantmentKey = "enchantment-modifiers." + sanitizeItemName(enchantment.getKey().asString());
            if (root.keyExists(enchantmentKey)) {
                price += root.getDouble(enchantmentKey) * item.getEnchantmentLevel(enchantment);
            } else {
                price += enchantmentModifier * item.getEnchantmentLevel(enchantment);
            }
        }
        return price;
    }

}
