package com.tiedup.remake.network.cell;

import com.tiedup.remake.blocks.BlockMarker;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.network.NetworkEvent;

/**
 * Packet for cell management actions.
 * Client -> Server
 */
public class PacketCellAction {

    /**
     * Action to perform on the cell/prisoner.
     */
    public enum Action {
        RELEASE, // Release a prisoner from cell
        TRANSFER, // Transfer a prisoner to another cell
        TELEPORT, // Teleport prisoner to cell
        DELETE_CELL, // Delete the cell
    }

    private final Action action;
    private final UUID cellId;
    private final UUID prisonerId; // For RELEASE/TRANSFER/TELEPORT
    private final UUID targetCellId; // For TRANSFER

    public PacketCellAction(
        Action action,
        UUID cellId,
        UUID prisonerId,
        UUID targetCellId
    ) {
        this.action = action;
        this.cellId = cellId;
        this.prisonerId = prisonerId;
        this.targetCellId = targetCellId;
    }

    public static void encode(PacketCellAction msg, FriendlyByteBuf buf) {
        buf.writeEnum(msg.action);
        buf.writeUUID(msg.cellId);
        buf.writeBoolean(msg.prisonerId != null);
        if (msg.prisonerId != null) {
            buf.writeUUID(msg.prisonerId);
        }
        buf.writeBoolean(msg.targetCellId != null);
        if (msg.targetCellId != null) {
            buf.writeUUID(msg.targetCellId);
        }
    }

    public static PacketCellAction decode(FriendlyByteBuf buf) {
        Action action = buf.readEnum(Action.class);
        UUID cellId = buf.readUUID();
        UUID prisonerId = buf.readBoolean() ? buf.readUUID() : null;
        UUID targetCellId = buf.readBoolean() ? buf.readUUID() : null;
        return new PacketCellAction(action, cellId, prisonerId, targetCellId);
    }

    public static void handle(
        PacketCellAction msg,
        Supplier<NetworkEvent.Context> ctx
    ) {
        ctx
            .get()
            .enqueueWork(() -> {
                ServerPlayer sender = ctx.get().getSender();
                if (sender == null) return;

                // CRITICAL FIX: Add rate limiting to prevent DoS via packet spam
                if (
                    !com.tiedup.remake.network.PacketRateLimiter.allowPacket(
                        sender,
                        "action"
                    )
                ) {
                    return;
                }

                CellRegistryV2 registry = CellRegistryV2.get(
                    sender.serverLevel()
                );
                CellDataV2 cell = registry.getCell(msg.cellId);

                if (cell == null) {
                    TiedUpMod.LOGGER.debug(
                        "[PacketCellAction] Cell not found: {}",
                        msg.cellId
                    );
                    return;
                }

                // Verify ownership
                if (
                    !cell.canPlayerManage(
                        sender.getUUID(),
                        sender.hasPermissions(2)
                    )
                ) {
                    TiedUpMod.LOGGER.debug(
                        "[PacketCellAction] Player is not cell owner"
                    );
                    return;
                }

                switch (msg.action) {
                    case RELEASE -> handleRelease(
                        sender,
                        registry,
                        cell,
                        msg.prisonerId
                    );
                    case TRANSFER -> handleTransfer(
                        sender,
                        registry,
                        cell,
                        msg.prisonerId,
                        msg.targetCellId
                    );
                    case TELEPORT -> handleTeleport(
                        sender,
                        registry,
                        cell,
                        msg.prisonerId
                    );
                    case DELETE_CELL -> handleDeleteCell(
                        sender,
                        registry,
                        cell
                    );
                }
            });

        ctx.get().setPacketHandled(true);
    }

    private static void handleRelease(
        ServerPlayer sender,
        CellRegistryV2 registry,
        CellDataV2 cell,
        UUID prisonerId
    ) {
        if (prisonerId == null) {
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] No prisoner ID for RELEASE"
            );
            return;
        }

        if (!cell.hasPrisoner(prisonerId)) {
            TiedUpMod.LOGGER.debug("[PacketCellAction] Prisoner not in cell");
            return;
        }

        // Release the prisoner from the cell (HIGH FIX: pass server for state cleanup)
        registry.releasePrisoner(cell.getId(), prisonerId, sender.getServer());

        // Get prisoner name for message
        String prisonerName = getPrisonerName(sender, prisonerId);

        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.PRISONER_RELEASED,
            prisonerName
        );

        TiedUpMod.LOGGER.info(
            "[PacketCellAction] {} released {} from cell {}",
            sender.getName().getString(),
            prisonerName,
            cell.getId().toString().substring(0, 8)
        );
    }

    private static void handleTransfer(
        ServerPlayer sender,
        CellRegistryV2 registry,
        CellDataV2 sourceCell,
        UUID prisonerId,
        UUID targetCellId
    ) {
        if (prisonerId == null || targetCellId == null) {
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] Missing IDs for TRANSFER"
            );
            return;
        }

        // Security: Verify the entity is actually a prisoner in the source cell
        if (!sourceCell.hasPrisoner(prisonerId)) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Entity is not a prisoner in the source cell"
            );
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] TRANSFER denied: entity {} is not prisoner in cell {}",
                prisonerId.toString().substring(0, 8),
                sourceCell.getId().toString().substring(0, 8)
            );
            return;
        }

        CellDataV2 targetCell = registry.getCell(targetCellId);
        if (targetCell == null) {
            TiedUpMod.LOGGER.debug("[PacketCellAction] Target cell not found");
            return;
        }

        // Verify ownership of target cell too
        if (
            !targetCell.canPlayerManage(
                sender.getUUID(),
                sender.hasPermissions(2)
            )
        ) {
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] Player doesn't own target cell"
            );
            return;
        }

        if (targetCell.isFull()) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Target cell is full"
            );
            return;
        }

        // Transfer (HIGH FIX: pass server for state cleanup)
        registry.releasePrisoner(
            sourceCell.getId(),
            prisonerId,
            sender.getServer()
        );

        // BUG FIX: Check if assignment succeeded to prevent data desync
        boolean assigned = registry.assignPrisoner(targetCellId, prisonerId);
        if (!assigned) {
            // Assignment failed - re-assign to source cell to prevent data loss
            registry.assignPrisoner(sourceCell.getId(), prisonerId);
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Failed to transfer prisoner - cell assignment failed"
            );
            TiedUpMod.LOGGER.error(
                "[PacketCellAction] Failed to assign prisoner {} to target cell {}",
                prisonerId,
                targetCellId
            );
            return;
        }

        String prisonerName = getPrisonerName(sender, prisonerId);
        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.INFO,
            prisonerName + " transferred to " + targetCell.getName()
        );

        TiedUpMod.LOGGER.info(
            "[PacketCellAction] {} transferred {} from cell {} to cell {}",
            sender.getName().getString(),
            prisonerName,
            sourceCell.getId().toString().substring(0, 8),
            targetCellId.toString().substring(0, 8)
        );
    }

    private static void handleTeleport(
        ServerPlayer sender,
        CellRegistryV2 registry,
        CellDataV2 cell,
        UUID prisonerId
    ) {
        if (prisonerId == null) {
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] No prisoner ID for TELEPORT"
            );
            return;
        }

        // Security: Verify the entity is actually a prisoner in this cell
        if (!cell.hasPrisoner(prisonerId)) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Entity is not a prisoner in this cell"
            );
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] TELEPORT denied: entity {} is not prisoner in cell {}",
                prisonerId.toString().substring(0, 8),
                cell.getId().toString().substring(0, 8)
            );
            return;
        }

        // Find the prisoner entity
        LivingEntity prisoner = findEntity(sender, prisonerId);
        if (prisoner == null) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Prisoner not found or offline"
            );
            return;
        }

        // Teleport to cell core position
        prisoner.teleportTo(
            cell.getCorePos().getX() + 0.5,
            cell.getCorePos().getY(),
            cell.getCorePos().getZ() + 0.5
        );

        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.INFO,
            prisoner.getName().getString() + " teleported to cell"
        );

        TiedUpMod.LOGGER.info(
            "[PacketCellAction] {} teleported {} to cell {}",
            sender.getName().getString(),
            prisoner.getName().getString(),
            cell.getId().toString().substring(0, 8)
        );
    }

    private static void handleDeleteCell(
        ServerPlayer sender,
        CellRegistryV2 registry,
        CellDataV2 cell
    ) {
        UUID cellId = cell.getId();
        String cellName =
            cell.getName() != null
                ? cell.getName()
                : cellId.toString().substring(0, 8);
        ServerLevel level = sender.serverLevel();

        // Release all prisoners first (HIGH FIX: pass server for state cleanup)
        for (UUID prisonerId : cell.getPrisonerIds()) {
            registry.releasePrisoner(cellId, prisonerId, sender.getServer());
        }

        // Remove the Cell Core block at the core position
        BlockPos corePos = cell.getCorePos();
        BlockState state = level.getBlockState(corePos);
        if (state.getBlock() instanceof BlockMarker) {
            // Destroy the core block (this also triggers cleanup in BlockMarker.onRemove)
            level.destroyBlock(corePos, false);
            TiedUpMod.LOGGER.debug(
                "[PacketCellAction] Destroyed Cell Core at {}",
                corePos.toShortString()
            );
        }

        // Remove the cell from registry (includes all linked positions)
        // Note: This may already be handled by BlockMarker.onRemove, but we do it
        // explicitly to ensure cleanup even if the marker block was already gone
        registry.removeCell(cellId);

        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.CELL_DELETED
        );

        TiedUpMod.LOGGER.info(
            "[PacketCellAction] {} deleted cell {}",
            sender.getName().getString(),
            cellName
        );
    }

    private static String getPrisonerName(
        ServerPlayer sender,
        UUID prisonerId
    ) {
        LivingEntity entity = findEntity(sender, prisonerId);
        return entity != null
            ? entity.getName().getString()
            : prisonerId.toString().substring(0, 8);
    }

    /**
     * CRITICAL FIX: Bounded entity search to prevent DoS.
     * Previous implementation used getAllEntities() which scans EVERY entity in the world.
     * New implementation uses spatial search with 200-block radius.
     */
    private static LivingEntity findEntity(ServerPlayer sender, UUID entityId) {
        // Try player first (fast path - O(1) lookup)
        Player player = sender.getServer().getPlayerList().getPlayer(entityId);
        if (player != null) return player;

        // CRITICAL FIX: Use bounded spatial search instead of getAllEntities()
        // Search within 200 blocks of the player (reasonable range for cell operations)
        final int SEARCH_RADIUS = 200;
        net.minecraft.world.phys.AABB searchBox =
            new net.minecraft.world.phys.AABB(
                sender.getX() - SEARCH_RADIUS,
                sender.getY() - SEARCH_RADIUS,
                sender.getZ() - SEARCH_RADIUS,
                sender.getX() + SEARCH_RADIUS,
                sender.getY() + SEARCH_RADIUS,
                sender.getZ() + SEARCH_RADIUS
            );

        // Search only LivingEntity instances within the bounded area
        for (LivingEntity entity : sender
            .serverLevel()
            .getEntitiesOfClass(LivingEntity.class, searchBox, e ->
                e.getUUID().equals(entityId)
            )) {
            return entity; // Found the entity
        }

        return null; // Entity not found within search radius
    }
}
