package net.Chipperfluff.chipi.armor;

import net.minecraft.block.BlockState;
import net.minecraft.block.SlabBlock;
import net.minecraft.block.StairsBlock;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

import net.Chipperfluff.chipi.item.ModItems;

import static net.Chipperfluff.chipi.server.ChipiServerEvents.CHIPI_DIMENSION_KEY;
import static net.Chipperfluff.chipi.util.ChipiTrackedData.CHIPI_ENERGY;

public final class ProtectionAuraHandler {

    private static final float BASE_RECHARGE_RATE = 0.0005f;
    private static final float BASE_DRAIN_RATE    = 0.0008f;

    private static final float CASCADE_LOSS = 0.30f;
    private static final int CRITICAL_PCT = 10;

    private ProtectionAuraHandler() {}

    /* ==========================================================
       ENTRY POINT
       ========================================================== */

    public static void tick(PlayerEntity player) {
        if (!hasFullChipperArmor(player)) return;

        Float energy = player.getDataTracker().get(CHIPI_ENERGY);
        if (energy == null) return;

        float durabilityFactor = getDurabilityFactor(player);
        if (durabilityFactor <= 0f) return;

        float nextEnergy = calculateNextEnergy(player, energy, durabilityFactor);
        if (nextEnergy == energy) return;

        applyEnergyDelta(player, energy, nextEnergy);
        player.getDataTracker().set(CHIPI_ENERGY, nextEnergy);
    }

    /* ==========================================================
       ENERGY
       ========================================================== */

    private static float calculateNextEnergy(PlayerEntity player, float current, float durabilityFactor) {
        boolean recharge = !isInChipi(player.getWorld()) || isOnProtectedBlock(player);

        float rate = recharge
            ? BASE_RECHARGE_RATE * durabilityFactor
            : BASE_DRAIN_RATE * (1.0f + (1.0f - durabilityFactor));

        return recharge
            ? Math.min(1.0f, current + rate)
            : Math.max(0.0f, current - rate);
    }

    private static void applyEnergyDelta(PlayerEntity player, float before, float after) {
        int oldPct = (int) (before * 100f);
        int newPct = (int) (after * 100f);
        int delta = Math.abs(newPct - oldPct);

        if (delta > 0) {
            applyArmorDamage(player, delta);
        }
    }

    /* ==========================================================
       ARMOR DAMAGE
       ========================================================== */

    private static void applyArmorDamage(PlayerEntity player, int amount) {
        int[] before = getArmorPercents(player);
        boolean broke = damageAllPieces(player, amount);

        if (broke) {
            handleCascade(player, before);
        } else {
            handleWarnings(player, before, getArmorPercents(player));
        }
    }

    private static boolean damageAllPieces(PlayerEntity player, int amount) {
        boolean broke = false;

        for (ItemStack stack : player.getArmorItems()) {
            if (stack.isEmpty()) continue;

            stack.damage(amount, player, p -> {});
            if (stack.getDamage() >= stack.getMaxDamage()) {
                broke = true;
            }
        }
        return broke;
    }

    /* ==========================================================
       CASCADE
       ========================================================== */

    private static void handleCascade(PlayerEntity player, int[] before) {
        applyCascadeLoss(player);
        player.getDataTracker().set(CHIPI_ENERGY, 0f);
        sendCollapseMessage(player, before, getArmorPercents(player));
    }

    private static void applyCascadeLoss(PlayerEntity player) {
        for (ItemStack stack : player.getArmorItems()) {
            if (stack.isEmpty()) continue;

            int max = stack.getMaxDamage();
            int remaining = max - stack.getDamage();
            if (remaining <= 0) continue;

            int extra = Math.round(remaining * CASCADE_LOSS);
            stack.setDamage(Math.min(max, stack.getDamage() + extra));
        }
    }

    /* ==========================================================
       WARNINGS
       ========================================================== */

    private static void handleWarnings(PlayerEntity player, int[] before, int[] after) {
        if (!enteredCritical(before, after)) return;
        player.sendMessage(Text.literal(buildWarningMessage(after)), false);
    }

    private static boolean enteredCritical(int[] before, int[] after) {
        for (int i = 0; i < 4; i++) {
            if (before[i] > CRITICAL_PCT && after[i] <= CRITICAL_PCT) {
                return true;
            }
        }
        return false;
    }

    /* ==========================================================
       CHAT BUILDERS
       ========================================================== */

    private static String buildWarningMessage(int[] pct) {
        StringBuilder sb = new StringBuilder("§6[AURA WARNING]\n");
        appendPiece(sb, "Head", pct[3]);
        appendPiece(sb, "Body", pct[2]);
        appendPiece(sb, "Legs", pct[1]);
        appendPiece(sb, "Feet", pct[0]);
        return sb.toString();
    }

    private static void sendCollapseMessage(PlayerEntity player, int[] before, int[] after) {
        StringBuilder sb = new StringBuilder("§c[AURA COLLAPSE]\n");
        appendPiece(sb, "Head", before[3], after[3]);
        appendPiece(sb, "Body", before[2], after[2]);
        appendPiece(sb, "Legs", before[1], after[1]);
        appendPiece(sb, "Feet", before[0], after[0]);
        player.sendMessage(Text.literal(sb.toString()), false);
    }

    private static void appendPiece(StringBuilder sb, String name, int pct) {
        sb.append("§7").append(name).append(": ").append(pct).append("%");
        if (pct <= CRITICAL_PCT) sb.append(" §c!!!");
        sb.append("\n");
    }

    private static void appendPiece(StringBuilder sb, String name, int before, int after) {
        sb.append("§7").append(name).append(": ")
          .append(after).append("% (was ").append(before).append("%)\n");
    }

    /* ==========================================================
       UTIL
       ========================================================== */

    private static int[] getArmorPercents(PlayerEntity player) {
        int[] out = new int[4];
        int i = 0;

        for (ItemStack stack : player.getArmorItems()) {
            if (stack.isEmpty()) {
                out[i++] = 0;
            } else {
                int max = stack.getMaxDamage();
                int cur = max - stack.getDamage();
                out[i++] = Math.max(0, (int) ((cur / (float) max) * 100f));
            }
        }
        return out;
    }

    private static float getDurabilityFactor(PlayerEntity player) {
        int max = 0, cur = 0;

        for (ItemStack stack : player.getArmorItems()) {
            if (!stack.isEmpty()) {
                max += stack.getMaxDamage();
                cur += stack.getMaxDamage() - stack.getDamage();
            }
        }
        return max <= 0 ? 0f : Math.min(1f, (float) cur / max);
    }

    /* ==========================================================
       CONDITIONS
       ========================================================== */

    public static boolean hasFullChipperArmor(PlayerEntity player) {
        PlayerInventory inv = player.getInventory();
        return inv.getArmorStack(3).isOf(ModItems.CHIPPER_HELMET)
            && inv.getArmorStack(2).isOf(ModItems.CHIPPER_CHESTPLATE)
            && inv.getArmorStack(1).isOf(ModItems.CHIPPER_LEGGINGS)
            && inv.getArmorStack(0).isOf(ModItems.CHIPPER_BOOTS);
    }

    private static boolean isOnProtectedBlock(PlayerEntity player) {
        BlockPos pos = player.getBlockPos();
        BlockState state = player.getWorld().getBlockState(pos);

        return state.contains(Properties.WATERLOGGED)
            && state.get(Properties.WATERLOGGED)
            && (state.getBlock() instanceof StairsBlock
             || state.getBlock() instanceof SlabBlock);
    }

    private static boolean isInChipi(World world) {
        return world.getRegistryKey().equals(CHIPI_DIMENSION_KEY);
    }

    public static boolean hasAura(PlayerEntity player) {
        if (!hasFullChipperArmor(player)) return false;
        Float v = player.getDataTracker().get(CHIPI_ENERGY);
        return v != null && v > 0f && getDurabilityFactor(player) > 0f;
    }
}
