package com.tiedup.remake.prison.service;

import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.cells.MarkerType;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.labor.LaborTask;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import org.jetbrains.annotations.Nullable;

/**
 * Centralized item management for the prison system.
 *
 * Handles:
 * - Confiscating valuables from new prisoners
 * - Confiscating contraband from working prisoners
 * - Collecting task items from completed labor
 * - Finding and managing camp storage chests
 */
public class ItemService {

    // Singleton instance
    private static final ItemService INSTANCE = new ItemService();

    public static ItemService get() {
        return INSTANCE;
    }

    private ItemService() {}

    // ==================== ITEM CATEGORIES ====================

    /** Items considered "valuables" to confiscate on imprisonment */
    private static final Set<Item> VALUABLE_ITEMS = Set.of(
        Items.DIAMOND,
        Items.EMERALD,
        Items.GOLD_INGOT,
        Items.IRON_INGOT,
        Items.NETHERITE_INGOT,
        Items.NETHERITE_SCRAP,
        Items.DIAMOND_BLOCK,
        Items.EMERALD_BLOCK,
        Items.GOLD_BLOCK,
        Items.IRON_BLOCK,
        Items.NETHERITE_BLOCK,
        Items.LAPIS_LAZULI,
        Items.LAPIS_BLOCK,
        Items.AMETHYST_SHARD
    );

    /** Items considered "contraband" to confiscate during labor */
    private static final Set<Item> CONTRABAND_ITEMS = Set.of(
        // Weapons
        Items.DIAMOND_SWORD,
        Items.IRON_SWORD,
        Items.GOLDEN_SWORD,
        Items.STONE_SWORD,
        Items.WOODEN_SWORD,
        Items.NETHERITE_SWORD,
        Items.BOW,
        Items.CROSSBOW,
        Items.TRIDENT,
        // Tools that can be weapons
        Items.DIAMOND_AXE,
        Items.IRON_AXE,
        Items.GOLDEN_AXE,
        Items.STONE_AXE,
        Items.WOODEN_AXE,
        Items.NETHERITE_AXE,
        Items.DIAMOND_PICKAXE,
        Items.NETHERITE_PICKAXE,
        // Escape items
        Items.ENDER_PEARL,
        Items.CHORUS_FRUIT,
        Items.ELYTRA,
        // Dangerous items
        Items.FLINT_AND_STEEL,
        Items.FIRE_CHARGE,
        Items.TNT
    );

    // ==================== CONFISCATION ====================

    /**
     * Confiscate valuable items from a new prisoner.
     * Called when a prisoner is first imprisoned.
     *
     * @param player The prisoner
     * @return List of confiscated items
     */
    public List<ItemStack> confiscateValuables(ServerPlayer player) {
        List<ItemStack> confiscated = new ArrayList<>();
        var inventory = player.getInventory();

        for (int i = 0; i < inventory.getContainerSize(); i++) {
            ItemStack stack = inventory.getItem(i);
            if (!stack.isEmpty() && isValuable(stack.getItem())) {
                confiscated.add(stack.copy());
                inventory.setItem(i, ItemStack.EMPTY);
            }
        }

        if (!confiscated.isEmpty()) {
            TiedUpMod.LOGGER.info(
                "[ItemService] Confiscated {} valuable stacks from {}",
                confiscated.size(),
                player.getName().getString()
            );
        }

        return confiscated;
    }

    /**
     * Confiscate contraband items from a working prisoner.
     * Called periodically during labor or when returning to cell.
     *
     * @param player The prisoner
     * @return List of confiscated items
     */
    public List<ItemStack> confiscateContraband(ServerPlayer player) {
        List<ItemStack> confiscated = new ArrayList<>();
        var inventory = player.getInventory();

        for (int i = 0; i < inventory.getContainerSize(); i++) {
            ItemStack stack = inventory.getItem(i);
            if (!stack.isEmpty() && isContraband(stack)) {
                confiscated.add(stack.copy());
                inventory.setItem(i, ItemStack.EMPTY);
            }
        }

        if (!confiscated.isEmpty()) {
            TiedUpMod.LOGGER.info(
                "[ItemService] Confiscated {} contraband stacks from {}",
                confiscated.size(),
                player.getName().getString()
            );
        }

        return confiscated;
    }

    /**
     * Check if an item is considered a valuable.
     */
    public boolean isValuable(Item item) {
        return VALUABLE_ITEMS.contains(item);
    }

    /**
     * Check if an item stack is contraband.
     * Excludes items tagged as LaborTool.
     */
    public boolean isContraband(ItemStack stack) {
        if (stack.isEmpty()) return false;

        // Labor tools are allowed
        if (isLaborTool(stack)) return false;

        return CONTRABAND_ITEMS.contains(stack.getItem());
    }

    /**
     * Check if an item is a labor tool (from the camp).
     */
    private boolean isLaborTool(ItemStack stack) {
        if (!stack.hasTag()) return false;
        var tag = stack.getTag();
        return tag != null && tag.getBoolean("LaborTool");
    }

    // ==================== TASK ITEM COLLECTION ====================

    /**
     * Collect task items from a worker's inventory.
     * Called by maid when task is complete.
     *
     * @param player The worker
     * @param task The completed task
     * @return List of collected item stacks
     */
    public List<ItemStack> collectTaskItems(
        ServerPlayer player,
        LaborTask task
    ) {
        return task.collectItems(player);
    }

    /**
     * Reclaim labor equipment from a worker.
     * Called when returning prisoner to cell.
     *
     * @param player The worker
     * @param task The task (for tool identification)
     * @return List of reclaimed tools (for storage)
     */
    public List<ItemStack> reclaimEquipment(
        ServerPlayer player,
        LaborTask task
    ) {
        return task.reclaimEquipment(player);
    }

    // ==================== CHEST MANAGEMENT ====================

    /**
     * Find the storage chest for a camp.
     *
     * Lookup order:
     * 1. CampData registered loot positions (fast path)
     * 2. Lazy discovery: scan 30-block radius around camp center for chests with LOOT markers above
     *    — registers found chests in CampData so future lookups are instant
     *
     * @param level The server level
     * @param campId The camp UUID
     * @return BlockPos of chest, or null if not found
     */
    @Nullable
    public BlockPos findCampChest(ServerLevel level, UUID campId) {
        com.tiedup.remake.cells.CampOwnership ownership =
            com.tiedup.remake.cells.CampOwnership.get(level);
        com.tiedup.remake.cells.CampOwnership.CampData camp = ownership.getCamp(
            campId
        );

        // Fast path: CampData has registered loot positions
        if (camp != null && !camp.getLootChestPositions().isEmpty()) {
            for (BlockPos pos : camp.getLootChestPositions()) {
                BlockEntity be = level.getBlockEntity(pos);
                if (be instanceof ChestBlockEntity) {
                    return pos;
                }
            }
        }

        // Lazy discovery: scan around camp center for chests with LOOT markers above
        if (camp != null && camp.getCenter() != null) {
            List<BlockPos> discovered = discoverLootChests(level, camp, 30);
            if (!discovered.isEmpty()) {
                return discovered.get(0);
            }
        }

        return null;
    }

    /**
     * Find all storage chests for a camp.
     *
     * @param level The server level
     * @param campId The camp UUID
     * @return List of chest positions
     */
    public List<BlockPos> findAllCampChests(ServerLevel level, UUID campId) {
        com.tiedup.remake.cells.CampOwnership ownership =
            com.tiedup.remake.cells.CampOwnership.get(level);
        com.tiedup.remake.cells.CampOwnership.CampData camp = ownership.getCamp(
            campId
        );
        if (camp == null) return List.of();

        // Fast path: CampData has registered loot positions
        if (!camp.getLootChestPositions().isEmpty()) {
            List<BlockPos> valid = new ArrayList<>();
            for (BlockPos pos : camp.getLootChestPositions()) {
                BlockEntity be = level.getBlockEntity(pos);
                if (be instanceof ChestBlockEntity) {
                    valid.add(pos);
                }
            }
            if (!valid.isEmpty()) return valid;
        }

        // Lazy discovery
        if (camp.getCenter() != null) {
            return discoverLootChests(level, camp, 30);
        }

        return List.of();
    }

    /**
     * Find the chest for a specific cell.
     *
     * @param level The server level
     * @param cellId The cell UUID
     * @return BlockPos of chest, or null if not found
     */
    @Nullable
    public BlockPos findCellChest(ServerLevel level, UUID cellId) {
        CellRegistryV2 registry = CellRegistryV2.get(level);
        CellDataV2 cell = registry.getCell(cellId);

        if (cell == null) return null;

        // V2: scan interior blocks for chests (no LOOT markers in V2)
        for (BlockPos pos : cell.getInteriorBlocks()) {
            BlockEntity be = level.getBlockEntity(pos);
            if (be instanceof ChestBlockEntity) {
                return pos;
            }
        }

        return null;
    }

    /**
     * Scan around camp center for chests with LOOT markers above them.
     * Registers found chests in CampData for future fast lookups.
     *
     * @param level The server level
     * @param camp The camp data
     * @param radius Horizontal search radius
     * @return List of discovered chest positions
     */
    private List<BlockPos> discoverLootChests(
        ServerLevel level,
        com.tiedup.remake.cells.CampOwnership.CampData camp,
        int radius
    ) {
        BlockPos center = camp.getCenter();
        List<BlockPos> found = new ArrayList<>();
        int yRange = 15;

        for (int x = -radius; x <= radius; x++) {
            for (int z = -radius; z <= radius; z++) {
                for (int y = -yRange; y <= yRange; y++) {
                    BlockPos pos = center.offset(x, y, z);
                    BlockEntity be = level.getBlockEntity(pos);
                    if (!(be instanceof ChestBlockEntity)) continue;

                    // Check for LOOT marker above
                    BlockPos above = pos.above();
                    BlockEntity markerBe = level.getBlockEntity(above);
                    if (
                        markerBe instanceof MarkerBlockEntity marker &&
                        marker.getMarkerType() == MarkerType.LOOT
                    ) {
                        found.add(pos);

                        // Register in CampData for future fast lookups
                        camp.addLootChestPosition(pos);
                    }
                }
            }
        }

        if (!found.isEmpty()) {
            com.tiedup.remake.cells.CampOwnership.get(level).setDirty();
            TiedUpMod.LOGGER.info(
                "[ItemService] Lazy discovery: found {} LOOT chests for camp {} (scanned {}r around {})",
                found.size(),
                camp.getCampId().toString().substring(0, 8),
                radius,
                center.toShortString()
            );
        }

        return found;
    }

    /**
     * Store items in a chest.
     *
     * @param level The server level
     * @param chestPos Position of the chest
     * @param items Items to store
     * @return Number of items that couldn't be stored (chest full)
     */
    public int storeInChest(
        ServerLevel level,
        BlockPos chestPos,
        List<ItemStack> items
    ) {
        BlockEntity be = level.getBlockEntity(chestPos);
        if (!(be instanceof Container container)) {
            return items.stream().mapToInt(ItemStack::getCount).sum();
        }

        int notStored = 0;

        for (ItemStack stack : items) {
            ItemStack remaining = insertIntoContainer(container, stack.copy());
            notStored += remaining.getCount();
        }

        return notStored;
    }

    /**
     * Retrieve a tool from a chest.
     *
     * @param level The server level
     * @param chestPos Position of the chest
     * @param toolType The type of tool to find
     * @return The tool stack, or empty if not found
     */
    public ItemStack retrieveToolFromChest(
        ServerLevel level,
        BlockPos chestPos,
        Item toolType
    ) {
        BlockEntity be = level.getBlockEntity(chestPos);
        if (!(be instanceof Container container)) {
            return ItemStack.EMPTY;
        }

        for (int i = 0; i < container.getContainerSize(); i++) {
            ItemStack stack = container.getItem(i);
            if (
                !stack.isEmpty() &&
                stack.getItem() == toolType &&
                isLaborTool(stack)
            ) {
                ItemStack retrieved = stack.copy();
                container.setItem(i, ItemStack.EMPTY);
                container.setChanged();
                return retrieved;
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Insert an item stack into a container.
     *
     * @param container The container
     * @param stack The stack to insert
     * @return Remaining items that couldn't be inserted
     */
    private ItemStack insertIntoContainer(
        Container container,
        ItemStack stack
    ) {
        if (stack.isEmpty()) return ItemStack.EMPTY;

        // First try to merge with existing stacks
        for (
            int i = 0;
            i < container.getContainerSize() && !stack.isEmpty();
            i++
        ) {
            ItemStack slotStack = container.getItem(i);
            if (
                !slotStack.isEmpty() &&
                ItemStack.isSameItemSameTags(slotStack, stack) &&
                slotStack.getCount() < slotStack.getMaxStackSize()
            ) {
                int space = slotStack.getMaxStackSize() - slotStack.getCount();
                int toAdd = Math.min(space, stack.getCount());
                slotStack.grow(toAdd);
                stack.shrink(toAdd);
            }
        }

        // Then try empty slots
        for (
            int i = 0;
            i < container.getContainerSize() && !stack.isEmpty();
            i++
        ) {
            if (container.getItem(i).isEmpty()) {
                container.setItem(i, stack.copy());
                stack.setCount(0);
            }
        }

        if (container instanceof BlockEntity blockEntity) {
            blockEntity.setChanged();
        }

        return stack;
    }

    /**
     * Clear prisoner's entire inventory except for armor.
     * Used for maximum security confinement.
     *
     * @param player The prisoner
     * @return All cleared items
     */
    public List<ItemStack> clearInventory(ServerPlayer player) {
        List<ItemStack> cleared = new ArrayList<>();
        var inventory = player.getInventory();

        // Clear main inventory and hotbar (slots 0-35)
        for (int i = 0; i < 36; i++) {
            ItemStack stack = inventory.getItem(i);
            if (!stack.isEmpty()) {
                cleared.add(stack.copy());
                inventory.setItem(i, ItemStack.EMPTY);
            }
        }

        // Clear offhand (slot 40)
        ItemStack offhand = inventory.offhand.get(0);
        if (!offhand.isEmpty()) {
            cleared.add(offhand.copy());
            inventory.offhand.set(0, ItemStack.EMPTY);
        }

        TiedUpMod.LOGGER.info(
            "[ItemService] Cleared {} stacks from {}'s inventory",
            cleared.size(),
            player.getName().getString()
        );

        return cleared;
    }
}
