package com.tiedup.remake.minigame;

import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Random;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;

/**
 * Server-side state for the continuous Struggle mini-game session.
 *
 * New system: Player holds a direction key to continuously reduce resistance.
 * - Progression: 1 resistance per second when holding correct direction
 * - Direction changes randomly every 3-5 seconds
 * - Shock collar check: 10% chance every 5 seconds (if locked collar equipped)
 * - No cooldown, no penalty for wrong key (just no progress)
 */
public class ContinuousStruggleMiniGameState {

    /**
     * Possible directions for struggling.
     */
    public enum Direction {
        UP(0), // W / Forward
        LEFT(1), // A / Strafe Left
        DOWN(2), // S / Back
        RIGHT(3); // D / Strafe Right

        private final int index;

        Direction(int index) {
            this.index = index;
        }

        public int getIndex() {
            return index;
        }

        public static Direction fromIndex(int index) {
            return switch (index) {
                case 0 -> UP;
                case 1 -> LEFT;
                case 2 -> DOWN;
                case 3 -> RIGHT;
                default -> UP;
            };
        }

        public static Direction random(Random random) {
            return fromIndex(random.nextInt(4));
        }
    }

    /**
     * State of the continuous struggle session.
     */
    public enum State {
        /** Actively struggling */
        ACTIVE,
        /** Temporarily interrupted by shock collar */
        SHOCKED,
        /** Successfully escaped (resistance reached 0) */
        ESCAPED,
        /** Cancelled by player or damage */
        CANCELLED,
    }

    /**
     * Type of state update sent to client.
     */
    public enum UpdateType {
        START,
        DIRECTION_CHANGE,
        RESISTANCE_UPDATE,
        SHOCK,
        ESCAPE,
        END,
    }

    // ==================== CONSTANTS ====================

    /** Minimum interval for direction change (3 seconds = 60 ticks) */
    private static final int DIRECTION_CHANGE_MIN_TICKS = 60;

    /** Maximum interval for direction change (5 seconds = 100 ticks) */
    private static final int DIRECTION_CHANGE_MAX_TICKS = 100;

    /** Interval for shock collar check (5 seconds = 100 ticks) */
    private static final int SHOCK_CHECK_INTERVAL_TICKS = 100;

    /** Shock probability (10% = 10 out of 100) */
    private static final int SHOCK_PROBABILITY = 10;

    /** Default ticks per 1 resistance point (20 ticks = 1 second per resistance) */
    private static final int DEFAULT_TICKS_PER_RESISTANCE = 20;

    /** Shock stun duration in ticks (1 second) */
    private static final int SHOCK_STUN_TICKS = 20;

    /** Kidnapper notification interval (2 seconds = 40 ticks) */
    private static final int KIDNAPPER_NOTIFY_INTERVAL_TICKS = 40;

    /** Struggle sound interval (1.5 seconds = 30 ticks) */
    private static final int STRUGGLE_SOUND_INTERVAL_TICKS = 30;

    /** Session timeout in milliseconds (10 minutes) */
    private static final long SESSION_TIMEOUT_MS = 10L * 60L * 1000L;

    // ==================== FIELDS ====================

    private final UUID sessionId;
    private final UUID playerId;
    private final long createdAt;

    /** Current required direction (0-3 for W/A/S/D) */
    private Direction currentDirection;

    /** Current resistance remaining */
    private int currentResistance;

    /** Maximum resistance (initial value) for display percentage */
    private final int maxResistance;

    /** Accumulated progress toward next resistance point (0.0 to 1.0) */
    private float accumulatedProgress;

    /** Tick when direction last changed */
    private long lastDirectionChangeTick;

    /** Tick when shock was last checked */
    private long lastShockCheckTick;

    /** Tick when kidnappers were last notified */
    private long lastKidnapperNotifyTick;

    /** Tick when struggle sound was last played */
    private long lastStruggleSoundTick;

    /** Ticks until direction change */
    private int ticksUntilDirectionChange;

    /** Current session state */
    private State state;

    /** Ticks remaining in shock stun */
    private int shockStunTicksRemaining;

    /** Whether player is currently holding the correct key */
    private boolean isHoldingCorrectKey;

    /** Direction player is currently holding (-1 if none) */
    private int heldDirection;

    /** Whether the bind is locked (affects display only, locks add +250 resistance) */
    private boolean isLocked;

    /** Target accessory slot (null = bind, otherwise = accessory) */
    private Integer targetSlot;

    /** Target V2 body region (null = V1 session, non-null = V2 session) */
    @Nullable
    private BodyRegionV2 targetRegion;

    /** Target furniture entity ID (non-zero = furniture struggle session) */
    private int furnitureEntityId;

    /** Target seat ID for furniture struggles (null = not a furniture session) */
    @Nullable
    private String furnitureSeatId;

    /** Resistance reduction per tick (computed from ticksPerResistance) */
    private final float resistancePerTick;

    private final Random random = new Random();

    // ==================== CONSTRUCTOR ====================

    public ContinuousStruggleMiniGameState(
        UUID playerId,
        int targetResistance,
        boolean isLocked
    ) {
        this(
            playerId,
            targetResistance,
            isLocked,
            null,
            DEFAULT_TICKS_PER_RESISTANCE
        );
    }

    public ContinuousStruggleMiniGameState(
        UUID playerId,
        int targetResistance,
        boolean isLocked,
        Integer targetSlot
    ) {
        this(
            playerId,
            targetResistance,
            isLocked,
            targetSlot,
            DEFAULT_TICKS_PER_RESISTANCE
        );
    }

    /**
     * Constructor with target slot and configurable rate.
     *
     * @param playerId Player UUID
     * @param targetResistance Current resistance
     * @param isLocked Whether the target is locked
     * @param targetSlot Target accessory slot (null = bind)
     * @param ticksPerResistance Ticks per 1 resistance point (from game rule)
     */
    public ContinuousStruggleMiniGameState(
        UUID playerId,
        int targetResistance,
        boolean isLocked,
        Integer targetSlot,
        int ticksPerResistance
    ) {
        this.sessionId = UUID.randomUUID();
        this.playerId = playerId;
        this.createdAt = System.currentTimeMillis();
        this.resistancePerTick = 1.0f / Math.max(1, ticksPerResistance);

        this.currentResistance = targetResistance;
        this.maxResistance = targetResistance;
        this.isLocked = isLocked;
        this.targetSlot = targetSlot;

        // Initialize direction
        this.currentDirection = Direction.random(random);
        this.accumulatedProgress = 0.0f;

        // Initialize timers (will be set properly on first tick)
        this.lastDirectionChangeTick = 0;
        this.lastShockCheckTick = 0;
        this.lastKidnapperNotifyTick = 0;
        this.ticksUntilDirectionChange = randomDirectionChangeInterval();

        // Initial state
        this.state = State.ACTIVE;
        this.shockStunTicksRemaining = 0;
        this.isHoldingCorrectKey = false;
        this.heldDirection = -1;
    }

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

    public UUID getSessionId() {
        return sessionId;
    }

    public UUID getPlayerId() {
        return playerId;
    }

    public Direction getCurrentDirection() {
        return currentDirection;
    }

    public int getCurrentDirectionIndex() {
        return currentDirection.getIndex();
    }

    public int getCurrentResistance() {
        return currentResistance;
    }

    public int getMaxResistance() {
        return maxResistance;
    }

    public float getProgressPercentage() {
        if (maxResistance == 0) return 0.0f;
        return 1.0f - ((float) currentResistance / (float) maxResistance);
    }

    public State getState() {
        return state;
    }

    public boolean isLocked() {
        return isLocked;
    }

    public Integer getTargetSlot() {
        return targetSlot;
    }

    public boolean isAccessoryStruggle() {
        return targetSlot != null;
    }

    /** Get the V2 target region (null for V1 sessions). */
    @Nullable
    public BodyRegionV2 getTargetRegion() {
        return targetRegion;
    }

    /** Set the V2 target region. */
    public void setTargetRegion(@Nullable BodyRegionV2 region) {
        this.targetRegion = region;
    }

    /** Whether this is a V2 region-based struggle session. */
    public boolean isV2Struggle() {
        return targetRegion != null;
    }

    /** Get the furniture entity ID (0 = not a furniture session). */
    public int getFurnitureEntityId() {
        return furnitureEntityId;
    }

    /** Get the furniture seat ID (null = not a furniture session). */
    @Nullable
    public String getFurnitureSeatId() {
        return furnitureSeatId;
    }

    /** Set furniture struggle context. */
    public void setFurnitureContext(int entityId, String seatId) {
        this.furnitureEntityId = entityId;
        this.furnitureSeatId = seatId;
    }

    /** Whether this is a furniture escape struggle session. */
    public boolean isFurnitureStruggle() {
        return furnitureEntityId != 0 && furnitureSeatId != null;
    }

    public boolean isActive() {
        return state == State.ACTIVE;
    }

    public boolean isShocked() {
        return state == State.SHOCKED;
    }

    public boolean isComplete() {
        return state == State.ESCAPED || state == State.CANCELLED;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() - createdAt > SESSION_TIMEOUT_MS;
    }

    public boolean isHoldingCorrectKey() {
        return isHoldingCorrectKey;
    }

    public int getHeldDirection() {
        return heldDirection;
    }

    // ==================== STATE UPDATES ====================

    /**
     * Update the held direction from client input.
     *
     * @param direction Direction index (-1 if not holding any key)
     * @param isHolding True if player is actively holding the key
     */
    public void updateHeldDirection(int direction, boolean isHolding) {
        this.heldDirection = direction;
        this.isHoldingCorrectKey =
            isHolding && (direction == currentDirection.getIndex());
    }

    /**
     * Process a server tick.
     *
     * @param currentTick Current game tick
     * @return True if state changed significantly (needs client update)
     */
    public TickResult tick(long currentTick) {
        if (isComplete()) {
            return TickResult.NO_CHANGE;
        }

        // Handle shock stun
        if (state == State.SHOCKED) {
            shockStunTicksRemaining--;
            if (shockStunTicksRemaining <= 0) {
                state = State.ACTIVE;
                return TickResult.SHOCK_END;
            }
            return TickResult.NO_CHANGE;
        }

        boolean needsUpdate = false;
        TickResult result = TickResult.NO_CHANGE;

        // Direction change check
        ticksUntilDirectionChange--;
        if (ticksUntilDirectionChange <= 0) {
            changeDirection(currentTick);
            result = TickResult.DIRECTION_CHANGE;
        }

        // Progress resistance if holding correct key
        if (isHoldingCorrectKey) {
            accumulatedProgress += resistancePerTick;

            if (accumulatedProgress >= 1.0f) {
                int resistanceReduced = (int) accumulatedProgress;
                accumulatedProgress -= resistanceReduced;
                currentResistance = Math.max(
                    0,
                    currentResistance - resistanceReduced
                );

                if (result == TickResult.NO_CHANGE) {
                    result = TickResult.RESISTANCE_UPDATE;
                }

                // Check for escape
                if (currentResistance <= 0) {
                    state = State.ESCAPED;
                    return TickResult.ESCAPED;
                }
            }
        }

        return result;
    }

    /**
     * Result of a tick update.
     */
    public enum TickResult {
        NO_CHANGE,
        DIRECTION_CHANGE,
        RESISTANCE_UPDATE,
        SHOCK_START,
        SHOCK_END,
        ESCAPED,
        KIDNAPPER_NOTIFY,
    }

    /**
     * Check if shock collar should trigger.
     *
     * @param currentTick Current game tick
     * @return True if shock should be triggered
     */
    public boolean shouldTriggerShock(long currentTick) {
        if (state != State.ACTIVE) return false;
        if (
            currentTick - lastShockCheckTick < SHOCK_CHECK_INTERVAL_TICKS
        ) return false;

        lastShockCheckTick = currentTick;
        return random.nextInt(100) < SHOCK_PROBABILITY;
    }

    /**
     * Trigger a shock event.
     */
    public void triggerShock() {
        if (state != State.ACTIVE) return;
        state = State.SHOCKED;
        shockStunTicksRemaining = SHOCK_STUN_TICKS;
        isHoldingCorrectKey = false;
    }

    /**
     * Check if kidnappers should be notified.
     *
     * @param currentTick Current game tick
     * @return True if kidnappers should be notified
     */
    public boolean shouldNotifyKidnappers(long currentTick) {
        if (state != State.ACTIVE || !isHoldingCorrectKey) return false;
        if (
            currentTick - lastKidnapperNotifyTick <
            KIDNAPPER_NOTIFY_INTERVAL_TICKS
        ) return false;

        lastKidnapperNotifyTick = currentTick;
        return true;
    }

    /**
     * Check if struggle sound should play.
     *
     * @param currentTick Current game tick
     * @return True if struggle sound should be played
     */
    public boolean shouldPlayStruggleSound(long currentTick) {
        if (state != State.ACTIVE || !isHoldingCorrectKey) return false;
        if (
            currentTick - lastStruggleSoundTick < STRUGGLE_SOUND_INTERVAL_TICKS
        ) return false;

        lastStruggleSoundTick = currentTick;
        return true;
    }

    /**
     * Cancel the session.
     */
    public void cancel() {
        state = State.CANCELLED;
    }

    // ==================== PRIVATE HELPERS ====================

    private void changeDirection(long currentTick) {
        Direction newDirection;
        do {
            newDirection = Direction.random(random);
        } while (newDirection == currentDirection);

        currentDirection = newDirection;
        lastDirectionChangeTick = currentTick;
        ticksUntilDirectionChange = randomDirectionChangeInterval();

        // LOW FIX: Always reset held key status to prevent lucky guess exploit
        // Player must release and re-press the key to react to direction change
        // even if they were already holding the new correct key
        isHoldingCorrectKey = false;
    }

    private int randomDirectionChangeInterval() {
        return (
            DIRECTION_CHANGE_MIN_TICKS +
            random.nextInt(
                DIRECTION_CHANGE_MAX_TICKS - DIRECTION_CHANGE_MIN_TICKS + 1
            )
        );
    }

    @Override
    public String toString() {
        return String.format(
            "ContinuousStruggleMiniGameState{session=%s, player=%s, dir=%s, resistance=%d/%d, state=%s}",
            sessionId.toString().substring(0, 8),
            playerId.toString().substring(0, 8),
            currentDirection,
            currentResistance,
            maxResistance,
            state
        );
    }
}
