package net.knarcraft.blacksmith.trait;

import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.NPCSettings;
import net.knarcraft.blacksmith.manager.EconomyManager;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;

import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import static net.knarcraft.blacksmith.util.MessageFormatter.sendNPCMessage;

/**
 * The class representing the Blacksmith NPC trait
 */
public class BlacksmithTrait extends Trait {

    private final Map<UUID, Calendar> coolDowns = new HashMap<>();
    private ReforgeSession session;
    private final NPCSettings config;
    private long _sessionStart = System.currentTimeMillis();

    /**
     * Instantiates a new blacksmith trait
     */
    public BlacksmithTrait() {
        super("blacksmith");
        //This should crash if the blacksmith plugin hasn't been properly registered
        Bukkit.getServer().getPluginManager().getPlugin("Blacksmith");
        this.config = new NPCSettings(BlacksmithPlugin.getInstance().getSettings());
    }

    /**
     * Gets the current settings for this NPC
     *
     * @return <p>The current settings for this NPC</p>
     */
    public NPCSettings getSettings() {
        return config;
    }

    /**
     * Gets whether this blacksmith is already in a re-forge session
     *
     * @return <p>Whether already in a re-forge session</p>
     */
    public boolean hasSession() {
        return this.session != null;
    }

    /**
     * Adds a cool-down for the given player's next blacksmith reforge
     *
     * @param playerUUID <p>The ID of the player to add the cool-down for</p>
     * @param waitUntil  <p>The time when the player can interact again</p>
     */
    public void addCoolDown(UUID playerUUID, Calendar waitUntil) {
        coolDowns.put(playerUUID, waitUntil);
    }

    /**
     * Unsets the session of this blacksmith, making it ready for another round
     */
    public void unsetSession() {
        this.session = null;
    }

    /**
     * Loads all config values stored in citizens' config file for this NPC
     *
     * @param key <p>The data key used for the config root</p>
     */
    @Override
    public void load(DataKey key) {
        config.loadVariables(key);
    }

    /**
     * Saves all config values for this NPC
     *
     * @param key <p>The data key used for the config root</p>
     */
    @Override
    public void save(DataKey key) {
        config.saveVariables(key);
    }

    /**
     * Performs necessary work before the session is started or continued
     *
     * @param player <p>The player to prepare the session for</p>
     * @return <p>True if preparations were successful. False if a session shouldn't be started</p>
     */
    public boolean prepareForSession(Player player) {
        //If cool-down has been disabled after it was set for this player, remove the cool-down
        if (config.getDisableCoolDown() && coolDowns.get(player.getUniqueId()) != null) {
            coolDowns.remove(player.getUniqueId());
        }
        //Deny if permission is missing
        if (!player.hasPermission("blacksmith.reforge")) {
            return false;
        }

        //Deny if on cool-down, or remove cool-down if expired
        if (coolDowns.get(player.getUniqueId()) != null) {
            if (!Calendar.getInstance().after(coolDowns.get(player.getUniqueId()))) {
                sendNPCMessage(this.npc, player, config.getCoolDownUnexpiredMessage());
                return false;
            }
            coolDowns.remove(player.getUniqueId());
        }

        //If already in a session, but the player has failed to interact, or left the blacksmith, allow a new session
        if (session != null) {
            if (System.currentTimeMillis() > _sessionStart + 10 * 1000 ||
                    this.npc.getEntity().getLocation().distance(session.getPlayer().getLocation()) > 20) {
                session = null;
            }
        }
        return true;
    }

    /**
     * Tries to continue the session for the given player
     *
     * @param player <p>The player to continue the session for</p>
     */
    public void continueSession(Player player) {
        //Another player is using the blacksmith
        if (!session.isInSession(player)) {
            sendNPCMessage(this.npc, player, config.getBusyWithPlayerMessage());
            return;
        }

        //The blacksmith is already reforging for the player
        if (session.isRunning()) {
            sendNPCMessage(this.npc, player, config.getBusyReforgingMessage());
            return;
        }
        if (session.endSession()) {
            //Quit if the player cannot afford, or has changed their item
            session = null;
        } else {
            //Start reforging for the player
            reforge(npc, player);
        }
    }

    /**
     * Starts a new session, and prepares to repair the player's item
     *
     * @param player <p>The player to start the session for</p>
     */
    public void startSession(Player player) {
        ItemStack hand = player.getInventory().getItemInMainHand();
        //Refuse if not repairable, or if reforge-able items is set, but doesn't include the held item
        List<Material> reforgeAbleItems = config.getReforgeAbleItems();
        if (!isRepairable(hand) || (!reforgeAbleItems.isEmpty() && !reforgeAbleItems.contains(hand.getType()))) {
            sendNPCMessage(this.npc, player, config.getInvalidItemMessage());
            return;
        }

        //Start a new reforge session for the player
        _sessionStart = System.currentTimeMillis();
        session = new ReforgeSession(this, player, npc, config);

        //Tell the player the cost of repairing the item
        String cost = EconomyManager.formatCost(player);
        String itemName = hand.getType().name().toLowerCase().replace('_', ' ');
        sendNPCMessage(this.npc, player, config.getCostMessage().replace("<price>", cost).replace("<item>", itemName));
    }

    /**
     * Starts reforging the player's item
     *
     * @param npc    <p>The NPC performing the reforge</p>
     * @param player <p>The player that initiated the reforge</p>
     */
    private void reforge(NPC npc, Player player) {
        sendNPCMessage(this.npc, player, config.getStartReforgeMessage());
        EconomyManager.withdraw(player);
        session.beginReforge();
        ItemStack heldItem = player.getInventory().getItemInMainHand();

        //Display the item in the NPC's hand
        if (npc.getEntity() instanceof Player) {
            ((Player) npc.getEntity()).getInventory().setItemInMainHand(heldItem);
        } else {
            Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(heldItem);
        }
        //Remove the item from the player's inventory
        player.getInventory().setItemInMainHand(null);
    }

    /**
     * Gets whether the given item is repairable
     *
     * @param item <p>The item to check</p>
     * @return <p>True if the item is repairable</p>
     */
    public static boolean isRepairable(ItemStack item) {
        return item.getItemMeta() instanceof Damageable && EnchantmentTarget.BREAKABLE.includes(item);
    }

}
