package com.tiedup.remake.prison;

import com.mojang.logging.LogUtils;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

/**
 *
 * Contains:
 * - Captive and captor IDs
 * - Demanded item and amount
 * - Deadline tick
 * - State (PENDING, PAID, EXPIRED, CANCELLED)
 * - Optional cell/prison association
 */
public class RansomData {

    private static final Logger LOGGER = LogUtils.getLogger();

    /**
     * Ransom state enumeration
     */
    public enum RansomState {
        /**
         * Ransom is active and awaiting payment
         */
        PENDING("pending"),

        /**
         * Ransom has been paid, captive can be released
         */
        PAID("paid"),

        /**
         * Deadline passed without payment
         */
        EXPIRED("expired"),

        /**
         * Ransom was cancelled (captive escaped, captor died, etc.)
         */
        CANCELLED("cancelled");

        private final String serializedName;

        RansomState(String serializedName) {
            this.serializedName = serializedName;
        }

        public String getSerializedName() {
            return serializedName;
        }

        public static RansomState fromString(String name) {
            for (RansomState state : values()) {
                if (state.serializedName.equalsIgnoreCase(name)) {
                    return state;
                }
            }
            return PENDING;
        }
    }

    /**
     * Ransom difficulty level (determines item type and amount)
     */
    public enum RansomDifficulty {
        EASY(Items.IRON_INGOT, 8, 16, 24000L), // 1 MC day = 20 min real time
        NORMAL(Items.GOLD_INGOT, 4, 8, 48000L), // 2 MC days
        HARD(Items.DIAMOND, 1, 3, 72000L); // 3 MC days

        private final Item demandItem;
        private final int minAmount;
        private final int maxAmount;
        private final long deadlineTicks;

        RansomDifficulty(
            Item demandItem,
            int minAmount,
            int maxAmount,
            long deadlineTicks
        ) {
            this.demandItem = demandItem;
            this.minAmount = minAmount;
            this.maxAmount = maxAmount;
            this.deadlineTicks = deadlineTicks;
        }

        public Item getDemandItem() {
            return demandItem;
        }

        public int getMinAmount() {
            return minAmount;
        }

        public int getMaxAmount() {
            return maxAmount;
        }

        public long getDeadlineTicks() {
            return deadlineTicks;
        }
    }

    private final UUID ransomId;
    private final UUID captiveId;
    private final UUID captorId;
    private final Item demandItem;
    private final int demandAmount;
    private final long createdTick;
    private final long deadlineTick;
    private final RansomDifficulty difficulty;

    private RansomState state;
    private int amountPaid;

    @Nullable
    private UUID cellId;

    @Nullable
    private BlockPos dropChestPos;

    public RansomData(
        UUID captiveId,
        UUID captorId,
        RansomDifficulty difficulty,
        long currentTick
    ) {
        this.ransomId = UUID.randomUUID();
        this.captiveId = captiveId;
        this.captorId = captorId;
        this.difficulty = difficulty;
        this.demandItem = difficulty.getDemandItem();
        this.demandAmount =
            difficulty.getMinAmount() +
            (int) (Math.random() *
                (difficulty.getMaxAmount() - difficulty.getMinAmount() + 1));
        this.createdTick = currentTick;
        this.deadlineTick = currentTick + difficulty.getDeadlineTicks();
        this.state = RansomState.PENDING;
        this.amountPaid = 0;
    }

    /**
     * Constructor for custom item/amount ransoms (used by camp labor system).
     *
     * @param captiveId The captive's UUID
     * @param captorId The captor's UUID (trader/maid)
     * @param demandItem The item type demanded (e.g., EMERALD)
     * @param demandAmount The amount demanded
     * @param deadlineTicks How long until deadline (0 = no deadline for labor)
     * @param currentTick The current game tick
     */
    public RansomData(
        UUID captiveId,
        UUID captorId,
        Item demandItem,
        int demandAmount,
        long deadlineTicks,
        long currentTick
    ) {
        this.ransomId = UUID.randomUUID();
        this.captiveId = captiveId;
        this.captorId = captorId;
        this.difficulty = RansomDifficulty.NORMAL; // Default for serialization
        this.demandItem = demandItem;
        this.demandAmount = demandAmount;
        this.createdTick = currentTick;
        // If deadlineTicks is 0 or negative, set very far in the future (labor has no deadline)
        this.deadlineTick =
            deadlineTicks > 0
                ? currentTick + deadlineTicks
                : currentTick + (365L * 24L * 60L * 60L * 20L); // 1 year in ticks
        this.state = RansomState.PENDING;
        this.amountPaid = 0;
    }

    // Private constructor for loading
    private RansomData(
        UUID ransomId,
        UUID captiveId,
        UUID captorId,
        Item demandItem,
        int demandAmount,
        long createdTick,
        long deadlineTick,
        RansomDifficulty difficulty,
        RansomState state,
        int amountPaid,
        @Nullable UUID cellId,
        @Nullable BlockPos dropChestPos
    ) {
        this.ransomId = ransomId;
        this.captiveId = captiveId;
        this.captorId = captorId;
        this.demandItem = demandItem;
        this.demandAmount = demandAmount;
        this.createdTick = createdTick;
        this.deadlineTick = deadlineTick;
        this.difficulty = difficulty;
        this.state = state;
        this.amountPaid = amountPaid;
        this.cellId = cellId;
        this.dropChestPos = dropChestPos;
    }

    // ==================== GETTERS ====================

    public UUID getRansomId() {
        return ransomId;
    }

    public UUID getCaptiveId() {
        return captiveId;
    }

    public UUID getCaptorId() {
        return captorId;
    }

    public Item getDemandItem() {
        return demandItem;
    }

    public int getDemandAmount() {
        return demandAmount;
    }

    public long getCreatedTick() {
        return createdTick;
    }

    public long getDeadlineTick() {
        return deadlineTick;
    }

    public RansomDifficulty getDifficulty() {
        return difficulty;
    }

    public RansomState getState() {
        return state;
    }

    public int getAmountPaid() {
        return amountPaid;
    }

    @Nullable
    public UUID getCellId() {
        return cellId;
    }

    @Nullable
    public BlockPos getDropChestPos() {
        return dropChestPos;
    }

    // ==================== SETTERS ====================

    public void setState(RansomState state) {
        this.state = state;
    }

    public void setCellId(@Nullable UUID cellId) {
        this.cellId = cellId;
    }

    public void setDropChestPos(@Nullable BlockPos pos) {
        this.dropChestPos = pos;
    }

    // ==================== LOGIC ====================

    /**
     * Check if the ransom is still active (pending).
     */
    public boolean isActive() {
        return state == RansomState.PENDING;
    }

    /**
     * Check if the deadline has passed.
     */
    public boolean isExpired(long currentTick) {
        return currentTick >= deadlineTick;
    }

    /**
     * Get remaining time in ticks.
     */
    public long getRemainingTicks(long currentTick) {
        return Math.max(0, deadlineTick - currentTick);
    }

    /**
     * Get remaining time as formatted string (e.g., "1d 12h 30m").
     */
    public String getRemainingTimeFormatted(long currentTick) {
        long remaining = getRemainingTicks(currentTick);
        long seconds = remaining / 20;
        long minutes = seconds / 60;
        long hours = minutes / 60;
        long days = hours / 24;

        minutes %= 60;
        hours %= 24;

        if (days > 0) {
            return String.format("%dd %dh %dm", days, hours, minutes);
        } else if (hours > 0) {
            return String.format("%dh %dm", hours, minutes);
        } else {
            return String.format("%dm", minutes);
        }
    }

    /**
     * Add payment towards the ransom.
     *
     * @param amount Amount of items paid
     * @return true if ransom is now fully paid
     */
    public boolean addPayment(int amount) {
        if (state != RansomState.PENDING) {
            return false;
        }

        amountPaid += amount;

        if (amountPaid >= demandAmount) {
            state = RansomState.PAID;
            return true;
        }

        return false;
    }

    /**
     * Increase the debt by reducing the amount paid (used for punishments).
     * Can make amountPaid negative, effectively increasing the total debt.
     *
     * @param amount Amount to increase the debt by
     */
    public void increaseDebt(int amount) {
        if (state != RansomState.PENDING) {
            return;
        }
        amountPaid -= amount;
    }

    /**
     * Get remaining amount to pay.
     */
    public int getRemainingAmount() {
        return Math.max(0, demandAmount - amountPaid);
    }

    /**
     * Get payment progress as percentage.
     */
    public float getPaymentProgress() {
        return ((float) amountPaid / demandAmount) * 100f;
    }

    // ==================== SERIALIZATION ====================

    public CompoundTag save() {
        CompoundTag tag = new CompoundTag();
        tag.putUUID("ransomId", ransomId);
        tag.putUUID("captiveId", captiveId);
        tag.putUUID("captorId", captorId);
        ResourceLocation demandItemKey = ForgeRegistries.ITEMS.getKey(
            demandItem
        );
        if (demandItemKey != null) {
            tag.putString("demandItem", demandItemKey.toString());
        } else {
            LOGGER.warn(
                "[RansomData] Unregistered demand item {}, falling back to iron_ingot",
                demandItem
            );
            tag.putString("demandItem", "minecraft:iron_ingot");
        }
        tag.putInt("demandAmount", demandAmount);
        tag.putLong("createdTick", createdTick);
        tag.putLong("deadlineTick", deadlineTick);
        tag.putString("difficulty", difficulty.name());
        tag.putString("state", state.getSerializedName());
        tag.putInt("amountPaid", amountPaid);
        if (cellId != null) {
            tag.putUUID("cellId", cellId);
        }
        if (dropChestPos != null) {
            tag.put("dropChestPos", NbtUtils.writeBlockPos(dropChestPos));
        }
        return tag;
    }

    public static RansomData load(CompoundTag tag) {
        UUID ransomId = tag.getUUID("ransomId");
        UUID captiveId = tag.getUUID("captiveId");
        UUID captorId = tag.getUUID("captorId");

        String itemKey = tag.getString("demandItem");
        Item demandItem;
        try {
            demandItem = ForgeRegistries.ITEMS.getValue(
                net.minecraft.resources.ResourceLocation.parse(itemKey)
            );
            if (demandItem == null) demandItem = Items.IRON_INGOT;
        } catch (Exception e) {
            LOGGER.warn(
                "[RansomData] Failed to parse demand item key '{}', falling back to iron_ingot",
                itemKey,
                e
            );
            demandItem = Items.IRON_INGOT;
        }

        int demandAmount = tag.getInt("demandAmount");
        long createdTick = tag.getLong("createdTick");
        long deadlineTick = tag.getLong("deadlineTick");

        RansomDifficulty difficulty;
        try {
            difficulty = RansomDifficulty.valueOf(tag.getString("difficulty"));
        } catch (IllegalArgumentException e) {
            LOGGER.warn(
                "[RansomData] Unknown difficulty '{}', falling back to NORMAL",
                tag.getString("difficulty")
            );
            difficulty = RansomDifficulty.NORMAL;
        }
        RansomState state = RansomState.fromString(tag.getString("state"));
        int amountPaid = tag.getInt("amountPaid");

        UUID cellId = tag.contains("cellId") ? tag.getUUID("cellId") : null;
        BlockPos dropChestPos = tag.contains("dropChestPos")
            ? NbtUtils.readBlockPos(tag.getCompound("dropChestPos"))
            : null;

        return new RansomData(
            ransomId,
            captiveId,
            captorId,
            demandItem,
            demandAmount,
            createdTick,
            deadlineTick,
            difficulty,
            state,
            amountPaid,
            cellId,
            dropChestPos
        );
    }

    @Override
    public String toString() {
        ResourceLocation demandItemKey = ForgeRegistries.ITEMS.getKey(
            demandItem
        );
        return String.format(
            "RansomData{id=%s, captive=%s, demand=%dx%s, state=%s, paid=%d/%d}",
            ransomId.toString().substring(0, 8),
            captiveId.toString().substring(0, 8),
            demandAmount,
            demandItemKey != null ? demandItemKey : "unknown",
            state,
            amountPaid,
            demandAmount
        );
    }
}
