package xyz.alexcrea.cuanvil.group

import io.delilaheve.CustomAnvil
import org.bukkit.NamespacedKey
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.api.EnchantmentApi
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
import java.util.*

class EnchantConflictManager {

    companion object {
        // Path for the enchantments list
        const val ENCH_LIST_PATH = "enchantments"

        // Path for list of enchantments conflicting before level
        //TODO add test and gui
        const val CONFLICT_AFTER_LEVEL_LIST_PATH = "conflict_after_level"

        // Path for list of enchantments conflicting before level
        //TODO add test and gui
        const val CONFLICT_BEFORE_LEVEL_LIST_PATH = "conflict_before_level"

        // Path for group list related to the conflict
        const val CONFLICT_GROUP_PATH = "notAffectedGroups"

        // Path for the maximum number of enchantment before validating the conflict
        const val ENCH_MAX_PATH = "maxEnchantmentBeforeConflict"

        // Default name for a joining group
        const val DEFAULT_GROUP_NAME = "joinedGroup"
    }

    lateinit var conflictList: ArrayList<EnchantConflictGroup>


    private fun warnBadKey(key: String) {
        CustomAnvil.instance.logger.warning("Invalid key $key for conflict: is not a conflict")
    }

    // Read and prepare all conflict
    fun prepareConflicts(config: ConfigurationSection, itemManager: ItemGroupManager) {
        conflictList = ArrayList()

        // Clear conflict if exist
        for (enchant in CAEnchantmentRegistry.getInstance().values()) {
            enchant.clearConflict()
        }

        val keys = config.getKeys(false)
        for (key in keys) {
            val section = config.getConfigurationSection(key)
            if (section == null) {
                warnBadKey(key)
                continue
            }
            val conflict = createConflict(section, itemManager, key)

            addConflict(conflict)
        }

    }

    fun addConflict(conflict: EnchantConflictGroup) {
        addConflictToEnchantments(conflict)
        conflictList.add(conflict)
    }

    fun removeConflict(conflict: EnchantConflictGroup) {
        removeConflictFromEnchantments(conflict)
        conflictList.remove(conflict)
    }

    // Add the conflict to enchantments
    private fun addConflictToEnchantments(conflict: EnchantConflictGroup) {
        conflict.getEnchants().forEach { enchant ->
            enchant.addConflict(conflict)
        }
    }

    // Remove the conflict from enchantments
    private fun removeConflictFromEnchantments(conflict: EnchantConflictGroup) {
        conflict.getEnchants().forEach { enchant ->
            enchant.removeConflict(conflict)
        }
    }

    // create and read a conflict from a yaml section
    private fun createConflict(
        section: ConfigurationSection,
        itemManager: ItemGroupManager,
        conflictName: String
    ): EnchantConflictGroup {
        // Create conflict
        val conflict = createConflictObject(section, itemManager, conflictName)
        // Read and add enchantment to conflict
        val enchantList = section.getStringList(ENCH_LIST_PATH)
        for (enchantName in enchantList) {
            val enchants = getEnchantByIdentifier(enchantName)
            if (enchants.isEmpty()) {
                CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict $conflictName")
                continue
            }
            conflict.addEnchantments(enchants)
        }
        if (conflict.getEnchants().isEmpty()) {
            CustomAnvil.instance.logger.warning("Conflict $conflictName do not have valid enchantment, it will not do anything")
        }

        val conflictsAfterLevel = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH)
        val conflictsAfterMap = conflict.getConflictAfters()
        fetchConditionalRestriction(conflictsAfterMap, conflictsAfterLevel, conflictName)

        val conflictsBeforeLevel = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH)
        val conflictsBeforeMap = conflict.getConflictsBefore()
        fetchConditionalRestriction(conflictsBeforeMap, conflictsBeforeLevel, conflictName)

        return conflict
    }

    private fun fetchConditionalRestriction(
        restrictions: MutableMap<CAEnchantment, Int>,
        section: ConfigurationSection?,
        conflictName: String
    ) {
        if (section == null) return
        for (enchantName in section.getKeys(false)) {
            val enchants = getEnchantByIdentifier(enchantName)
            if (enchants.isEmpty()) {
                CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conditional restriction for conflict $conflictName")
                continue
            }

            val value = section.getInt(enchantName, -1)
            if (value < 0) continue

            for (enchant in enchants) {
                val previous = restrictions.getOrDefault(enchant, value)
                restrictions[enchant] = value.coerceAtMost(previous)
            }
        }
    }

    private fun getEnchantByIdentifier(enchantName: String): List<CAEnchantment> {
        val key = NamespacedKey.fromString(enchantName)
        if (key != null) {
            val enchantment = CAEnchantment.getByKey(key)
            if (enchantment != null) return Collections.singletonList(enchantment)

        }

        return CAEnchantment.getByName(enchantName)
    }


    private fun createConflictObject(
        section: ConfigurationSection,
        itemManager: ItemGroupManager,
        conflictName: String
    ): EnchantConflictGroup {
        // Get the maximum number of enchantment before validating the conflict
        var minBeforeBlock = section.getInt(ENCH_MAX_PATH, 0)
        if (minBeforeBlock < 0) {
            minBeforeBlock = 0
            CustomAnvil.instance.logger.warning("Conflict $conflictName have an invalid value of $ENCH_MAX_PATH")
            CustomAnvil.instance.logger.warning("It should be more or equal to 0. default to 0")
        }
        // Find or create the selected group for the conflict
        val groupList = section.getStringList(CONFLICT_GROUP_PATH)
        val finalGroup = IncludeGroup(DEFAULT_GROUP_NAME)
        for (groupName in groupList) {
            finalGroup.addToPolicy(findGroup(groupName, itemManager, conflictName))
        }

        // Return conflict
        return EnchantConflictGroup(conflictName, finalGroup, minBeforeBlock)
    }

    private fun findGroup(
        groupName: String,
        itemManager: ItemGroupManager,
        conflictName: String
    ): AbstractMaterialGroup {
        val group = itemManager.get(groupName)
        if (group == null) {
            CustomAnvil.instance.logger.warning("Material group $groupName do not exist but is ask by conflict $conflictName")
            return IncludeGroup("error_placeholder")
        }

        return group
    }

    fun isConflicting(
        appliedEnchants: Map<CAEnchantment, Int>,
        item: ItemStack,
        newEnchant: CAEnchantment
    ): ConflictType {
        val type = item.customType
        CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${type}")
        val conflictList = newEnchant.conflicts

        var result = ConflictType.NO_CONFLICT
        for (conflict in conflictList) {
            val isBigConflict = conflict.getEnchants().size > 1
            if (result == ConflictType.ITEM_CONFLICT && !isBigConflict) {
                CustomAnvil.verboseLog("skipping small conflict ${conflict.name}")
                continue
            }

            val allowed = conflict.allowed(appliedEnchants, type)
            CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ")
            if (!allowed) {
                if (conflict.getEnchants().size <= 1) {
                    result = ConflictType.ITEM_CONFLICT
                    CustomAnvil.verboseLog("Small conflict (${conflict.name}), continuing")
                } else {
                    CustomAnvil.verboseLog("Big conflict (${conflict.name}), stopping")
                    return ConflictType.ENCHANTMENT_CONFLICT
                }
            }
        }

        val immutableEnchants = Collections.unmodifiableMap(appliedEnchants)
        for (appliedEnchant in appliedEnchants.keys) {
            if (appliedEnchant is AdditionalTestEnchantment) {
                val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, type)
                if (doConflict) {
                    CustomAnvil.verboseLog("Big conflict by additional test, stopping")
                    return ConflictType.ENCHANTMENT_CONFLICT
                }

            }
        }

        if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) {
            val partialItem = item.clone()
            EnchantmentApi.setEnchantments(partialItem, immutableEnchants)

            if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) {
                return ConflictType.ITEM_CONFLICT
            }

        }

        return result
    }

}

/**
 * Provide information about the current conflict.
 */
enum class ConflictType(private val importance: Int) {
    /**
     * Allowed to proceed the anvil process.
     */
    NO_CONFLICT(0),

    /**
     * Inform that the anvil process should not change the current applied enchantment.
     */
    ITEM_CONFLICT(1),

    /**
     * Inform that the anvil process should not change the current applied enchantment.
     * Also add sacrificeIllegalCost for every enchantment marked as big conflict.
     */
    ENCHANTMENT_CONFLICT(2);

    fun getWorstConflict(otherConflict: ConflictType): ConflictType {
        return if (this.importance > otherConflict.importance) this
        else otherConflict

    }

}