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.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.util.KidnappedHelper;
import java.util.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;

/**
 * Prisoner lifecycle service.
 *
 * Manages prisoner state transitions atomically:
 * - PrisonerManager (SavedData state)
 * - CellRegistryV2 (cell assignment)
 * - Leash/captor refs (IBondageState / ICaptor)
 *
 * Handles: capture, imprison, extractFromCell, returnToCell, transferCaptive.
 * Escape detection and release are handled by {@link EscapeMonitorService}.
 *
 * AI goals manage: Navigation, Teleport, Equipment, Dialogue.
 * PrisonerService manages: State transitions + leash coordination.
 */
public class PrisonerService {

    private static final PrisonerService INSTANCE = new PrisonerService();

    public static PrisonerService get() {
        return INSTANCE;
    }

    private PrisonerService() {}

    // ==================== CAPTURE ====================

    /**
     * Capture a free entity.
     * FREE → CAPTURED + leash + captor refs.
     *
     * @param level Server level
     * @param captive The entity being captured (must have IBondageState state)
     * @param captor The capturing entity
     * @return true if capture succeeded
     */
    public boolean capture(
        ServerLevel level,
        LivingEntity captive,
        ICaptor captor
    ) {
        UUID captiveId = captive.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        long gameTime = level.getGameTime();

        // 1. PrisonerManager state transition: FREE → CAPTURED
        if (
            !manager.capture(captiveId, captor.getEntity().getUUID(), gameTime)
        ) {
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] capture() failed PrisonerManager transition for {}",
                captiveId.toString().substring(0, 8)
            );
            return false;
        }

        // 2. Leash attach
        IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
            captive
        );
        if (kidnappedState == null) {
            // Rollback
            manager.escape(
                captiveId,
                gameTime,
                "capture rollback: no kidnapped state"
            );
            return false;
        }

        boolean leashed;
        if (captive instanceof AbstractTiedUpNpc npc) {
            // NPC: use forceCapturedBy to bypass canCapture() PrisonerManager check
            // (PrisonerManager state is already CAPTURED at this point)
            leashed = npc.forceCapturedBy(captor);
        } else {
            // Player: getCapturedBy checks isEnslavable() first (OR logic),
            // so canCapture() PrisonerManager check is bypassed if tied up.
            // Generic IBondageState: same path.
            leashed = kidnappedState.getCapturedBy(captor);
        }

        if (!leashed) {
            // Rollback PrisonerManager
            manager.escape(
                captiveId,
                gameTime,
                "capture rollback: leash failed"
            );
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] capture() leash failed for {}, rolled back",
                captiveId.toString().substring(0, 8)
            );
            return false;
        }

        TiedUpMod.LOGGER.info(
            "[PrisonerService] Captured {} by {}",
            captive.getName().getString(),
            captor.getEntity().getName().getString()
        );
        return true;
    }

    // ==================== IMPRISONMENT ====================

    /**
     * Imprison a captured entity in a cell.
     * CAPTURED → IMPRISONED + unleash + CellRegistryV2.assign.
     *
     * The caller handles: teleport, confiscation, bind, dialogue.
     *
     * @param level Server level
     * @param captive The captured entity
     * @param captor The current captor
     * @param campId Camp UUID
     * @param cell The target cell
     * @return true if imprisonment succeeded
     */
    public boolean imprison(
        ServerLevel level,
        LivingEntity captive,
        ICaptor captor,
        UUID campId,
        CellDataV2 cell
    ) {
        if (cell == null) {
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] imprison() called with null cell"
            );
            return false;
        }

        UUID captiveId = captive.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
        long gameTime = level.getGameTime();

        // 1. Assign to cell registry
        boolean assigned = cellRegistry.assignPrisoner(cell.getId(), captiveId);
        if (!assigned) {
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] imprison() cell assignment failed for {}",
                captiveId.toString().substring(0, 8)
            );
            return false;
        }

        // Defense: if prisoner is FREE, capture first
        boolean defenseCaptured = false;
        PrisonerRecord record = manager.getRecord(captiveId);
        if (record.getState() == PrisonerState.FREE) {
            TiedUpMod.LOGGER.info(
                "[PrisonerService] {} is FREE, capturing before imprison",
                captive.getName().getString()
            );
            manager.capture(captiveId, captor.getEntity().getUUID(), gameTime);
            defenseCaptured = true;
        }

        // 2. PrisonerManager state transition: CAPTURED → IMPRISONED
        boolean imprisoned = manager.imprison(
            captiveId,
            campId,
            cell.getId(),
            gameTime
        );
        if (!imprisoned) {
            // Rollback cell assignment
            cellRegistry.releasePrisoner(
                cell.getId(),
                captiveId,
                level.getServer()
            );
            // Rollback defense auto-capture if we triggered it
            if (defenseCaptured) {
                manager.escape(captiveId, gameTime, "imprison_rollback");
            }
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] imprison() PrisonerManager transition failed for {} (state={})",
                captiveId.toString().substring(0, 8),
                manager.getState(captiveId)
            );
            return false;
        }

        // 3. Unleash: remove captor refs and drop leash
        IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
            captive
        );
        if (kidnappedState != null) {
            kidnappedState.free(false); // Drop leash without dialogue/items
            captor.removeCaptive(kidnappedState, false);
        }

        // 4. Sync PersonalityState cellId for damsels
        if (captive instanceof EntityDamsel damsel) {
            PersonalityState pState = damsel.getPersonalityState();
            if (pState != null) {
                pState.assignCell(cell.getId(), cell, damsel.getUUID());
            }
        }

        TiedUpMod.LOGGER.info(
            "[PrisonerService] Imprisoned {} in cell {} (camp={})",
            captive.getName().getString(),
            cell.getId().toString().substring(0, 8),
            campId != null ? campId.toString().substring(0, 8) : "null"
        );
        return true;
    }

    // ==================== EXTRACTION ====================

    /**
     * Extract a prisoner from their cell.
     * IMPRISONED → WORKING + CellRegistryV2.release + leash to new captor.
     *
     * For: dogwalk, maid fetch, labor extract.
     * Note: If the entity is not tracked by PrisonerManager (e.g., damsels),
     * the PrisonerManager step is skipped — only CellRegistryV2 + leash are handled.
     *
     * @param level Server level
     * @param captive The imprisoned entity
     * @param newCaptor The entity extracting (kidnapper or maid)
     * @param cell The cell to extract from
     * @return true if extraction succeeded
     */
    public boolean extractFromCell(
        ServerLevel level,
        LivingEntity captive,
        ICaptor newCaptor,
        CellDataV2 cell
    ) {
        UUID captiveId = captive.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
        long gameTime = level.getGameTime();

        // 1. PrisonerManager: IMPRISONED → WORKING (players only — damsels aren't tracked)
        boolean isPlayer = captive instanceof Player;
        if (isPlayer) {
            if (!manager.extract(captiveId, gameTime)) {
                TiedUpMod.LOGGER.warn(
                    "[PrisonerService] extractFromCell() PrisonerManager transition failed for {}",
                    captiveId.toString().substring(0, 8)
                );
                return false;
            }
        }

        // 2. Release from cell registry
        cellRegistry.releasePrisoner(
            cell.getId(),
            captiveId,
            level.getServer()
        );

        // 3. Leash to new captor — forceCapturedBy for damsels
        IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
            captive
        );
        if (kidnappedState == null) {
            // Rollback
            if (isPlayer) {
                manager.returnToCell(captiveId, cell.getId(), gameTime);
            }
            cellRegistry.assignPrisoner(cell.getId(), captiveId);
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] extractFromCell() no kidnapped state for {}, rolled back",
                captiveId.toString().substring(0, 8)
            );
            return false;
        }

        boolean leashed;
        if (captive instanceof AbstractTiedUpNpc npc) {
            leashed = npc.forceCapturedBy(newCaptor);
        } else {
            leashed = kidnappedState.getCapturedBy(newCaptor);
        }

        if (!leashed) {
            // Rollback: return to IMPRISONED + reassign cell
            if (isPlayer) {
                manager.returnToCell(captiveId, cell.getId(), gameTime);
            }
            cellRegistry.assignPrisoner(cell.getId(), captiveId);
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] extractFromCell() leash failed for {}, rolled back",
                captiveId.toString().substring(0, 8)
            );
            return false;
        }

        TiedUpMod.LOGGER.info(
            "[PrisonerService] Extracted {} from cell {} by {}",
            captive.getName().getString(),
            cell.getId().toString().substring(0, 8),
            newCaptor.getEntity().getName().getString()
        );
        return true;
    }

    // ==================== RETURN TO CELL ====================

    /**
     * Return a prisoner to their cell.
     * WORKING → IMPRISONED + unleash + CellRegistryV2.assign.
     *
     * For: dogwalk return, labor return.
     * Note: If the entity is not tracked by PrisonerManager (e.g., damsels),
     * the PrisonerManager step is skipped — only unleash + CellRegistryV2 are handled.
     *
     * @param level Server level
     * @param captive The working entity
     * @param currentCaptor The current captor holding the leash
     * @param cell The cell to return to
     * @return true if return succeeded
     */
    public boolean returnToCell(
        ServerLevel level,
        LivingEntity captive,
        @Nullable ICaptor currentCaptor,
        CellDataV2 cell
    ) {
        UUID captiveId = captive.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
        long gameTime = level.getGameTime();

        // 1. Unleash
        IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
            captive
        );
        if (kidnappedState != null && kidnappedState.isCaptive()) {
            kidnappedState.free(false);
        }
        if (currentCaptor != null && kidnappedState != null) {
            currentCaptor.removeCaptive(kidnappedState, false);
        }

        // 2. PrisonerManager: WORKING → IMPRISONED (players only — damsels aren't tracked)
        if (captive instanceof Player) {
            boolean returned = manager.returnToCell(
                captiveId,
                cell.getId(),
                gameTime
            );
            if (!returned) {
                // State might not be WORKING (escape during return, etc.)
                // Force-repair: try capture+imprison path
                PrisonerRecord record = manager.getRecord(captiveId);
                PrisonerState currentState = record.getState();
                TiedUpMod.LOGGER.warn(
                    "[PrisonerService] returnToCell() transition failed for {} (state={}). Force-repairing.",
                    captiveId.toString().substring(0, 8),
                    currentState
                );

                if (currentState == PrisonerState.FREE) {
                    UUID captorId =
                        currentCaptor != null
                            ? currentCaptor.getEntity().getUUID()
                            : captiveId;
                    manager.capture(captiveId, captorId, gameTime);
                    UUID campId = record.getCampId();
                    manager.imprison(captiveId, campId, cell.getId(), gameTime);
                } else if (currentState == PrisonerState.CAPTURED) {
                    UUID campId = record.getCampId();
                    manager.imprison(captiveId, campId, cell.getId(), gameTime);
                }
                // If IMPRISONED already — no transition needed, just reassign cell
            }
        }

        // 3. Reassign to cell registry
        cellRegistry.assignPrisoner(cell.getId(), captiveId);

        TiedUpMod.LOGGER.info(
            "[PrisonerService] Returned {} to cell {}",
            captive.getName().getString(),
            cell.getId().toString().substring(0, 8)
        );
        return true;
    }

    // ==================== TRANSFER ====================

    /**
     * Transfer a captive from one captor to another.
     * PrisonerManager state unchanged.
     *
     * For: maid→buyer, kidnapper→kidnapper.
     *
     * @param level Server level
     * @param captive The captive entity
     * @param oldCaptor The current captor
     * @param newCaptor The new captor
     * @return true if transfer succeeded
     */
    public boolean transferCaptive(
        ServerLevel level,
        LivingEntity captive,
        ICaptor oldCaptor,
        ICaptor newCaptor
    ) {
        IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
            captive
        );
        if (kidnappedState == null) {
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] transferCaptive() no kidnapped state for {}",
                captive.getName().getString()
            );
            return false;
        }

        // 1. Remove from old captor
        oldCaptor.removeCaptive(kidnappedState, false);

        // 2. Clear leash
        kidnappedState.free(false);

        // 3. Re-leash to new captor
        boolean leashed;
        if (captive instanceof AbstractTiedUpNpc npc) {
            leashed = npc.forceCapturedBy(newCaptor);
        } else {
            leashed = kidnappedState.getCapturedBy(newCaptor);
        }

        if (!leashed) {
            // Rollback: try to re-leash to old captor
            TiedUpMod.LOGGER.warn(
                "[PrisonerService] transferCaptive() re-leash to new captor failed, attempting rollback"
            );
            if (captive instanceof AbstractTiedUpNpc npc) {
                npc.forceCapturedBy(oldCaptor);
            } else {
                kidnappedState.getCapturedBy(oldCaptor);
            }
            return false;
        }

        TiedUpMod.LOGGER.info(
            "[PrisonerService] Transferred {} from {} to {}",
            captive.getName().getString(),
            oldCaptor.getEntity().getName().getString(),
            newCaptor.getEntity().getName().getString()
        );
        return true;
    }

}
