package com.tiedup.remake.v2.blocks;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.sync.PacketSyncPetBedState;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;

/**
 * Server-side tracker for pet bed sit/sleep state per player.
 * Manages immobilization, block entity occupancy, and client sync.
 */
public class PetBedManager {

    public enum PetBedMode {
        SIT,
        SLEEP,
    }

    private static final UUID PET_BED_SPEED_MODIFIER_UUID = UUID.fromString(
        "a1b2c3d4-e5f6-4789-abcd-ef0123456789"
    );
    private static final String PET_BED_SPEED_MODIFIER_NAME =
        "tiedup.pet_bed_immobilize";
    private static final double FULL_IMMOBILIZATION = -0.10;

    /** Active pet bed entries keyed by player UUID */
    private static final Map<UUID, PetBedEntry> entries =
        new ConcurrentHashMap<>();

    private static class PetBedEntry {

        final BlockPos pos;
        PetBedMode mode;

        PetBedEntry(BlockPos pos, PetBedMode mode) {
            this.pos = pos;
            this.mode = mode;
        }
    }

    /**
     * Set a player's pet bed mode (SIT or SLEEP).
     */
    public static void setMode(
        ServerPlayer player,
        BlockPos pos,
        PetBedMode mode
    ) {
        UUID uuid = player.getUUID();
        PetBedEntry existing = entries.get(uuid);

        if (existing != null && mode == PetBedMode.SLEEP) {
            // Transitioning from SIT to SLEEP — update mode
            existing.mode = mode;
        } else {
            // New entry (Stand → SIT)
            entries.put(uuid, new PetBedEntry(pos.immutable(), mode));
        }

        // Occupy the block entity
        BlockEntity be = player.level().getBlockEntity(pos);
        if (be instanceof PetBedBlockEntity petBed) {
            petBed.occupy(uuid);
        }

        // Teleport player to bed center and set rotation to bed facing
        double cx = pos.getX() + 0.5;
        double cy = pos.getY();
        double cz = pos.getZ() + 0.5;

        // Nudge player slightly backward on the bed
        float bedYRot = getBedFacingAngle(player.level(), pos);
        double rad = Math.toRadians(bedYRot);
        double backOffset = 0.15;
        cx += Math.sin(rad) * backOffset;
        cz -= Math.cos(rad) * backOffset;

        player.teleportTo(cx, cy, cz);

        player.setYRot(bedYRot);
        player.setYBodyRot(bedYRot);
        player.setYHeadRot(bedYRot);

        // Immobilize the player (both SIT and SLEEP)
        applySpeedModifier(player);

        // Sync to all clients
        PacketSyncPetBedState packet = new PacketSyncPetBedState(
            uuid,
            mode == PetBedMode.SIT ? (byte) 1 : (byte) 2,
            pos
        );
        ModNetwork.sendToAllTrackingAndSelf(packet, player);

        TiedUpMod.LOGGER.debug(
            "[PetBedManager] {} -> {} at {}",
            player.getName().getString(),
            mode,
            pos
        );
    }

    /**
     * Clear a player from the pet bed system.
     */
    public static void clearPlayer(ServerPlayer player) {
        UUID uuid = player.getUUID();
        PetBedEntry entry = entries.remove(uuid);
        if (entry == null) return;

        // Release block entity
        BlockEntity be = player.level().getBlockEntity(entry.pos);
        if (be instanceof PetBedBlockEntity petBed) {
            petBed.release();
        }

        // Restore speed
        removeSpeedModifier(player);

        // Sync clear to clients
        PacketSyncPetBedState packet = new PacketSyncPetBedState(
            uuid,
            (byte) 0,
            entry.pos
        );
        ModNetwork.sendToAllTrackingAndSelf(packet, player);

        TiedUpMod.LOGGER.debug(
            "[PetBedManager] {} cleared from pet bed at {}",
            player.getName().getString(),
            entry.pos
        );
    }

    /**
     * Check if a player is on a pet bed at the given position.
     */
    public static boolean isOnBed(ServerPlayer player, BlockPos pos) {
        PetBedEntry entry = entries.get(player.getUUID());
        return entry != null && entry.pos.equals(pos);
    }

    /**
     * Check if a player is on any pet bed.
     */
    public static boolean isOnAnyBed(ServerPlayer player) {
        return entries.containsKey(player.getUUID());
    }

    /**
     * Get the current mode for a player, or null if not on a bed.
     */
    @Nullable
    public static PetBedMode getMode(ServerPlayer player) {
        PetBedEntry entry = entries.get(player.getUUID());
        return entry != null ? entry.mode : null;
    }

    /**
     * Tick a player to check if pet bed state should be cancelled.
     * Called from server-side tick handler.
     */
    public static void tickPlayer(ServerPlayer player) {
        PetBedEntry entry = entries.get(player.getUUID());
        if (entry == null) return;

        // SLEEP: detect vanilla wakeup (night skip, etc.)
        if (entry.mode == PetBedMode.SLEEP && !player.isSleeping()) {
            clearPlayer(player);
            return;
        }

        // Both modes: lock body rotation to bed facing (prevents camera from rotating model)
        float bedYRot = getBedFacingAngle(player.level(), entry.pos);
        player.setYBodyRot(bedYRot);

        // SLEEP: enforce correct Y position (vanilla startSleeping sets Y+0.2)
        if (entry.mode == PetBedMode.SLEEP) {
            double correctY = entry.pos.getY();
            if (Math.abs(player.getY() - correctY) > 0.01) {
                player.teleportTo(player.getX(), correctY, player.getZ());
            }
            return;
        }

        // SIT: cancel on sneak (like dismounting a vehicle)
        if (entry.mode != PetBedMode.SIT) return;

        if (player.isShiftKeyDown()) {
            clearPlayer(player);
            return;
        }

        // Check if player moved too far from bed
        double distSq = player.blockPosition().distSqr(entry.pos);
        if (distSq > 2.25) {
            // > 1.5 blocks
            clearPlayer(player);
            return;
        }

        // Check if the block is still a pet bed
        if (
            !(player.level().getBlockState(entry.pos).getBlock() instanceof
                    PetBedBlock)
        ) {
            clearPlayer(player);
        }
    }

    private static float getBedFacingAngle(Level level, BlockPos pos) {
        BlockState state = level.getBlockState(pos);
        if (state.hasProperty(PetBedBlock.FACING)) {
            return state.getValue(PetBedBlock.FACING).toYRot();
        }
        return 0f;
    }

    /**
     * Clean up when a player disconnects.
     */
    public static void onPlayerDisconnect(UUID uuid) {
        entries.remove(uuid);
    }

    /**
     * Remove any leftover pet bed speed modifier on login.
     * The modifier persists on the entity through save/load, but the entries map doesn't,
     * so we clean it up here to prevent the player from being stuck.
     */
    public static void onPlayerLogin(ServerPlayer player) {
        removeSpeedModifier(player);
    }

    private static void applySpeedModifier(ServerPlayer player) {
        AttributeInstance movementSpeed = player.getAttribute(
            Attributes.MOVEMENT_SPEED
        );
        if (movementSpeed == null) return;

        // Remove existing to avoid duplicates
        if (movementSpeed.getModifier(PET_BED_SPEED_MODIFIER_UUID) != null) {
            movementSpeed.removeModifier(PET_BED_SPEED_MODIFIER_UUID);
        }

        AttributeModifier modifier = new AttributeModifier(
            PET_BED_SPEED_MODIFIER_UUID,
            PET_BED_SPEED_MODIFIER_NAME,
            FULL_IMMOBILIZATION,
            AttributeModifier.Operation.ADDITION
        );
        movementSpeed.addPermanentModifier(modifier);
    }

    private static void removeSpeedModifier(ServerPlayer player) {
        AttributeInstance movementSpeed = player.getAttribute(
            Attributes.MOVEMENT_SPEED
        );
        if (
            movementSpeed != null &&
            movementSpeed.getModifier(PET_BED_SPEED_MODIFIER_UUID) != null
        ) {
            movementSpeed.removeModifier(PET_BED_SPEED_MODIFIER_UUID);
        }
    }
}
