package com.tiedup.remake.prison.service;

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.*;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;

/**
 * Escape monitoring service.
 *
 * Handles all escape detection and processing:
 * - Periodic tick-based escape checks (distance, collar, offline timeout)
 * - On-demand escape checks (player movement)
 * - Central escape/release methods with full cleanup
 *
 * Split from PrisonerService to separate lifecycle transitions from escape monitoring.
 */
public class EscapeMonitorService {

    private static final EscapeMonitorService INSTANCE = new EscapeMonitorService();

    public static EscapeMonitorService get() {
        return INSTANCE;
    }

    private EscapeMonitorService() {}

    // ==================== CONSTANTS (from EscapeService) ====================

    /** Maximum distance from cell for IMPRISONED prisoners (blocks) */
    public static final double CELL_ESCAPE_DISTANCE = 20.0;

    /** Maximum distance from camp for WORKING prisoners (blocks) */
    public static final double WORK_ESCAPE_DISTANCE = 100.0;

    /** Maximum time offline before escape (30 minutes in ticks) */
    public static final long OFFLINE_TIMEOUT_TICKS = 30 * 60 * 20L;

    /** Maximum time in WORKING state before forced return (10 minutes in ticks) */
    public static final long WORK_TIMEOUT_TICKS = 10 * 60 * 20L;

    /** Maximum time in RETURNING phase before escape (5 minutes in ticks) */
    public static final long RETURN_TIMEOUT_TICKS = 5 * 60 * 20L;

    /** Maximum time in PENDING_RETURN phase before forced return (2.5 minutes in ticks) */
    public static final long PENDING_RETURN_TIMEOUT_TICKS = 3000;

    /** Check interval in ticks (every 5 seconds) */
    public static final int CHECK_INTERVAL_TICKS = 100;

    // ==================== ESCAPE ====================

    /**
     * CENTRAL ESCAPE METHOD - All escapes must go through here.
     *
     * Handles complete cleanup:
     * - State transition (PrisonerManager)
     * - Cell registry cleanup
     * - Guard despawn
     * - Restraints removal (if online)
     * - Inventory NOT restored (stays in camp chest as punishment)
     *
     * @param level The server level
     * @param playerId Player UUID
     * @param reason Reason for escape (for logging)
     * @return true if escape was processed successfully
     */
    public boolean escape(ServerLevel level, UUID playerId, String reason) {
        long currentTime = level.getGameTime();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cellRegistry = CellRegistryV2.get(level);

        TiedUpMod.LOGGER.info(
            "[EscapeMonitorService] Processing escape for {} - reason: {}",
            playerId.toString().substring(0, 8),
            reason
        );

        // Step 1: Save guard ID BEFORE state transition (manager.escape() removes the LaborRecord)
        // Use direct map lookup to avoid creating ghost LaborRecord entries for non-WORKING prisoners
        LaborRecord laborBeforeEscape = manager.getLaborRecordIfExists(playerId);
        UUID guardId = laborBeforeEscape != null ? laborBeforeEscape.getGuardId() : null;

        // Step 2: Transition prisoner state to FREE
        boolean stateChanged = manager.escape(playerId, currentTime, reason);
        if (!stateChanged) {
            TiedUpMod.LOGGER.warn(
                "[EscapeMonitorService] Failed to change state for {} - invalid transition",
                playerId.toString().substring(0, 8)
            );
            return false;
        }

        // Step 3: Cleanup CellRegistryV2 - remove from all cells
        int cellsCleared = cellRegistry.releasePrisonerFromAllCells(playerId);
        if (cellsCleared > 0) {
            TiedUpMod.LOGGER.debug(
                "[EscapeMonitorService] Cleared {} from {} cells",
                playerId.toString().substring(0, 8),
                cellsCleared
            );
        }

        // Step 4: Cleanup guard using saved reference (LaborRecord was removed by manager.escape())
        if (guardId != null) {
            net.minecraft.world.entity.Entity guardEntity = level.getEntity(
                guardId
            );
            if (guardEntity != null) {
                guardEntity.discard();
                TiedUpMod.LOGGER.debug(
                    "[EscapeMonitorService] Despawned guard {} during escape",
                    guardId.toString().substring(0, 8)
                );
            }
        }

        // Step 5: Free from restraints (if player is online)
        // NOTE: Inventory is NOT restored on escape - items remain in camp chest as punishment
        ServerPlayer player = level
            .getServer()
            .getPlayerList()
            .getPlayer(playerId);
        if (player != null) {
            IBondageState cap = KidnappedHelper.getKidnappedState(player);
            if (cap != null) {
                cap.free(false);
                TiedUpMod.LOGGER.info(
                    "[EscapeMonitorService] Freed {} from restraints (inventory remains in camp chest)",
                    player.getName().getString()
                );
            }
        } else {
            TiedUpMod.LOGGER.debug(
                "[EscapeMonitorService] Player {} offline - restraints cleanup deferred",
                playerId.toString().substring(0, 8)
            );
        }

        TiedUpMod.LOGGER.info(
            "[EscapeMonitorService] Escape complete for {} - items stay in camp chest",
            playerId.toString().substring(0, 8)
        );

        return true;
    }

    // ==================== RELEASE ====================

    /**
     * CENTRAL RELEASE METHOD - All legitimate releases must go through here.
     *
     * Similar to escape() but transitions to PROTECTED state with grace period.
     * Handles complete cleanup:
     * - State transition to PROTECTED (PrisonerManager)
     * - Cell registry cleanup
     * - Inventory restoration
     * - Restraints removal (if online)
     *
     * @param level The server level
     * @param playerId Player UUID
     * @param gracePeriodTicks Grace period before returning to FREE (0 = instant FREE)
     * @return true if release was processed successfully
     */
    public boolean release(
        ServerLevel level,
        UUID playerId,
        long gracePeriodTicks
    ) {
        long currentTime = level.getGameTime();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
        com.tiedup.remake.cells.ConfiscatedInventoryRegistry inventoryRegistry =
            com.tiedup.remake.cells.ConfiscatedInventoryRegistry.get(level);

        TiedUpMod.LOGGER.info(
            "[EscapeMonitorService] Processing release for {} - grace period: {} ticks",
            playerId.toString().substring(0, 8),
            gracePeriodTicks
        );

        // Step 1: Transition prisoner state to PROTECTED (or FREE if gracePeriod = 0)
        boolean stateChanged = manager.release(
            playerId,
            currentTime,
            gracePeriodTicks
        );
        if (!stateChanged) {
            TiedUpMod.LOGGER.warn(
                "[EscapeMonitorService] Failed to change state for {} - invalid transition",
                playerId.toString().substring(0, 8)
            );
            return false;
        }

        // Step 2: Cleanup CellRegistryV2 - remove from all cells
        int cellsCleared = cellRegistry.releasePrisonerFromAllCells(playerId);
        if (cellsCleared > 0) {
            TiedUpMod.LOGGER.debug(
                "[EscapeMonitorService] Cleared {} from {} cells",
                playerId.toString().substring(0, 8),
                cellsCleared
            );
        }

        // Step 3: Restore confiscated inventory (if player is online)
        ServerPlayer player = level
            .getServer()
            .getPlayerList()
            .getPlayer(playerId);
        if (player != null) {
            // Restore inventory
            if (inventoryRegistry.hasConfiscatedInventory(playerId)) {
                boolean restored = inventoryRegistry.restoreInventory(player);
                if (restored) {
                    TiedUpMod.LOGGER.info(
                        "[EscapeMonitorService] Restored confiscated inventory for {}",
                        player.getName().getString()
                    );
                }
            }

            // Free from restraints
            IBondageState cap = KidnappedHelper.getKidnappedState(player);
            if (cap != null) {
                cap.free(false);
                TiedUpMod.LOGGER.debug(
                    "[EscapeMonitorService] Freed {} from restraints",
                    player.getName().getString()
                );
            }
        } else {
            TiedUpMod.LOGGER.debug(
                "[EscapeMonitorService] Player {} offline - inventory cleanup deferred",
                playerId.toString().substring(0, 8)
            );
        }

        TiedUpMod.LOGGER.info(
            "[EscapeMonitorService] Release complete for {} - full cleanup done",
            playerId.toString().substring(0, 8)
        );

        return true;
    }

    // ==================== TICK (escape detection) ====================

    /**
     * Tick escape detection.
     * Called from CampManagementHandler.
     *
     * @param server The server
     * @param currentTime Current game time
     */
    public void tick(MinecraftServer server, long currentTime) {
        if (currentTime % CHECK_INTERVAL_TICKS != 0) {
            return;
        }

        ServerLevel level = server.overworld();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cells = CellRegistryV2.get(level);
        CollarRegistry collars = CollarRegistry.get(level);

        List<EscapeCandidate> escapees = new ArrayList<>();

        for (UUID playerId : manager.getAllPrisonerIds()) {
            PrisonerRecord record = manager.getRecord(playerId);
            PrisonerState state = record.getState();

            if (!state.isCaptive()) {
                continue;
            }

            ServerPlayer player = server.getPlayerList().getPlayer(playerId);

            // === OFFLINE CHECK ===
            if (player == null) {
                long timeInState = record.getTimeInState(currentTime);
                if (timeInState > OFFLINE_TIMEOUT_TICKS) {
                    escapees.add(
                        new EscapeCandidate(playerId, "offline timeout")
                    );
                }
                continue;
            }

            // === COLLAR CHECK ===
            if (state != PrisonerState.CAPTURED) {
                boolean hasCollarOwners = collars.hasOwners(playerId);
                if (!hasCollarOwners) {
                    IBondageState cap = KidnappedHelper.getKidnappedState(
                        player
                    );
                    ItemStack collar =
                        cap != null
                            ? cap.getEquipment(BodyRegionV2.NECK)
                            : ItemStack.EMPTY;

                    if (
                        !collar.isEmpty() &&
                        com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
                    ) {
                        List<UUID> nbtOwners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
                        if (!nbtOwners.isEmpty()) {
                            for (UUID ownerUUID : nbtOwners) {
                                collars.registerCollar(playerId, ownerUUID);
                            }
                            TiedUpMod.LOGGER.info(
                                "[EscapeMonitorService] Re-synced collar registry for {} - found {} owners in NBT",
                                playerId.toString().substring(0, 8),
                                nbtOwners.size()
                            );
                            continue;
                        }
                    }

                    TiedUpMod.LOGGER.warn(
                        "[EscapeMonitorService] Collar check failed for {} (state={}): collarEquipped={}, collarItem={}",
                        playerId.toString().substring(0, 8),
                        state,
                        !collar.isEmpty(),
                        collar.isEmpty() ? "none" : collar.getItem().toString()
                    );
                    escapees.add(
                        new EscapeCandidate(playerId, "collar removed")
                    );
                    continue;
                }
            }

            // === STATE-SPECIFIC CHECKS ===
            switch (state) {
                case IMPRISONED -> {
                    UUID cellId = record.getCellId();
                    if (cellId != null) {
                        CellDataV2 cell = cells.getCell(cellId);
                        if (cell == null) {
                            escapees.add(
                                new EscapeCandidate(
                                    playerId,
                                    "cell no longer exists"
                                )
                            );
                            TiedUpMod.LOGGER.warn(
                                "[EscapeMonitorService] Prisoner {} has orphaned cellId {} - triggering escape",
                                playerId.toString().substring(0, 8),
                                cellId.toString().substring(0, 8)
                            );
                        } else {
                            double distance = Math.sqrt(
                                player
                                    .blockPosition()
                                    .distSqr(cell.getCorePos())
                            );
                            if (distance > CELL_ESCAPE_DISTANCE) {
                                escapees.add(
                                    new EscapeCandidate(
                                        playerId,
                                        String.format(
                                            "too far from cell (%.1f blocks)",
                                            distance
                                        )
                                    )
                                );
                            }
                        }
                    }
                }
                case WORKING -> {
                    LaborRecord labor = manager.getLaborRecord(playerId);

                    long timeInPhase = labor.getTimeInPhase(currentTime);
                    if (
                        labor.getPhase() == LaborRecord.WorkPhase.WORKING &&
                        timeInPhase > WORK_TIMEOUT_TICKS
                    ) {
                        labor.failTask(currentTime);
                        TiedUpMod.LOGGER.info(
                            "[EscapeMonitorService] {} work timeout - forcing return",
                            playerId.toString().substring(0, 8)
                        );
                        continue;
                    }

                    if (
                        labor.getPhase() == LaborRecord.WorkPhase.RETURNING &&
                        timeInPhase > RETURN_TIMEOUT_TICKS
                    ) {
                        escapees.add(
                            new EscapeCandidate(playerId, "return timeout")
                        );
                        continue;
                    }

                    // PENDING_RETURN timeout: maid failed to pick up prisoner — force return to cell
                    if (
                        labor.getPhase() ==
                            LaborRecord.WorkPhase.PENDING_RETURN &&
                        timeInPhase > PENDING_RETURN_TIMEOUT_TICKS
                    ) {
                        PrisonerRecord rec = manager.getRecord(playerId);
                        UUID cellId = rec.getCellId();
                        if (cellId != null) {
                            CellDataV2 cell = cells.getCell(cellId);
                            if (cell != null) {
                                BlockPos teleportPos =
                                    cell.getSpawnPoint() != null
                                        ? cell.getSpawnPoint()
                                        : cell.getCorePos().above();
                                player.teleportTo(
                                    teleportPos.getX() + 0.5,
                                    teleportPos.getY(),
                                    teleportPos.getZ() + 0.5
                                );
                                IBondageState cap =
                                    KidnappedHelper.getKidnappedState(player);
                                if (cap != null) {
                                    CompoundTag snapshot =
                                        labor.getBondageSnapshot();
                                    if (snapshot != null) {
                                        BondageService.get().restoreSnapshot(
                                            cap,
                                            snapshot
                                        );
                                    }
                                }
                                PrisonerService.get().returnToCell(level, player, null, cell);
                                labor.startRest(currentTime);
                                if (labor.getGuardId() != null) {
                                    net.minecraft.world.entity.Entity guardEntity =
                                        level.getEntity(labor.getGuardId());
                                    if (
                                        guardEntity != null
                                    ) guardEntity.discard();
                                }
                                player.sendSystemMessage(
                                    Component.translatable(
                                        "msg.tiedup.prison.returned_to_cell"
                                    ).withStyle(ChatFormatting.GRAY)
                                );
                                TiedUpMod.LOGGER.info(
                                    "[EscapeMonitorService] Force-returned {} to cell (PENDING_RETURN timeout)",
                                    playerId.toString().substring(0, 8)
                                );
                                continue;
                            }
                        }
                        // Cell gone → trigger escape
                        escapees.add(
                            new EscapeCandidate(
                                playerId,
                                "pending_return timeout, cell gone"
                            )
                        );
                        continue;
                    }

                    if (labor.getGuardId() != null) {
                        net.minecraft.world.entity.Entity guardEntity =
                            level.getEntity(labor.getGuardId());
                        if (guardEntity != null && guardEntity.isAlive()) {
                            continue;
                        }
                    }

                    UUID campId = record.getCampId();
                    if (campId != null) {
                        List<CellDataV2> campCells = cells.getCellsByCamp(
                            campId
                        );
                        if (campCells.isEmpty()) {
                            escapees.add(
                                new EscapeCandidate(
                                    playerId,
                                    "camp no longer exists"
                                )
                            );
                            TiedUpMod.LOGGER.warn(
                                "[EscapeMonitorService] Worker {} has orphaned campId {} with no cells - triggering escape",
                                playerId.toString().substring(0, 8),
                                campId.toString().substring(0, 8)
                            );
                        } else {
                            BlockPos campCenter = campCells.get(0).getCorePos();
                            double distance = Math.sqrt(
                                player.blockPosition().distSqr(campCenter)
                            );
                            if (distance > WORK_ESCAPE_DISTANCE) {
                                escapees.add(
                                    new EscapeCandidate(
                                        playerId,
                                        String.format(
                                            "too far from camp (%.1f blocks)",
                                            distance
                                        )
                                    )
                                );
                            }
                        }
                    }
                }
                case CAPTURED -> {
                    // During transport - handled by kidnapper goals
                }
                default -> {
                    // Other states don't need distance checks
                }
            }
        }

        for (EscapeCandidate candidate : escapees) {
            escape(level, candidate.playerId, candidate.reason);
        }
    }

    // ==================== VALIDATION ====================

    /**
     * Check if a player should be considered escaped.
     * Called on-demand (e.g., when player moves).
     */
    @Nullable
    public String checkEscape(ServerLevel level, ServerPlayer player) {
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cells = CellRegistryV2.get(level);
        CollarRegistry collars = CollarRegistry.get(level);

        UUID playerId = player.getUUID();
        PrisonerRecord record = manager.getRecord(playerId);
        PrisonerState state = record.getState();

        if (!state.isCaptive()) {
            return null;
        }

        if (!collars.hasOwners(playerId)) {
            return "collar removed";
        }

        switch (state) {
            case IMPRISONED -> {
                UUID cellId = record.getCellId();
                if (cellId != null) {
                    CellDataV2 cell = cells.getCell(cellId);
                    if (cell != null) {
                        double distance = Math.sqrt(
                            player.blockPosition().distSqr(cell.getCorePos())
                        );
                        if (distance > CELL_ESCAPE_DISTANCE) {
                            return String.format(
                                "too far from cell (%.1f blocks)",
                                distance
                            );
                        }
                    }
                }
            }
            case WORKING -> {
                UUID campId = record.getCampId();
                if (campId != null) {
                    List<CellDataV2> campCells = cells.getCellsByCamp(campId);
                    if (!campCells.isEmpty()) {
                        BlockPos campCenter = campCells.get(0).getCorePos();
                        double distance = Math.sqrt(
                            player.blockPosition().distSqr(campCenter)
                        );
                        if (distance > WORK_ESCAPE_DISTANCE) {
                            return String.format(
                                "too far from camp (%.1f blocks)",
                                distance
                            );
                        }
                    }
                }
            }
            default -> {
                // Other states don't have distance limits
            }
        }

        return null;
    }

    /**
     * Handle player movement event.
     * Called from event handler to check distance-based escapes.
     */
    public void onPlayerMove(ServerPlayer player, ServerLevel level) {
        String escapeReason = checkEscape(level, player);
        if (escapeReason != null) {
            escape(level, player.getUUID(), escapeReason);
        }
    }

    // ==================== HELPER ====================

    private record EscapeCandidate(UUID playerId, String reason) {}
}
