package com.tiedup.remake.worldgen;

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapperElite;
import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.entities.ModEntities;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.TemplateStructurePiece;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;

/**
 * Structure piece for kidnapper camp tents.
 *
 * Phase: Kidnapper Revamp - Cell System
 *
 * Handles data markers:
 * - "kidnapper" - Spawns a regular kidnapper
 * - "kidnapper_elite" - Spawns an elite kidnapper
 * - "slave_trader" - Spawns a slave trader (brain of the camp)
 * - "maid" - Spawns a maid (linked to trader)
 * - "loot" - Places a loot chest
 */
public class KidnapperCampPiece extends TemplateStructurePiece {

    /** Temporary storage for trader UUID during structure generation */
    @Nullable
    private UUID spawnedTraderUUID;

    /** Temporary storage for camp data during structure generation */
    @Nullable
    private CampOwnership.CampData currentCamp;

    public KidnapperCampPiece(
        StructureTemplateManager templateManager,
        ResourceLocation templateLocation,
        BlockPos pos,
        Rotation rotation
    ) {
        super(
            ModStructures.CAMP_PIECE.get(),
            0,
            templateManager,
            templateLocation,
            templateLocation.toString(),
            makeSettings(rotation),
            pos
        );
    }

    public KidnapperCampPiece(
        StructureTemplateManager templateManager,
        CompoundTag tag
    ) {
        super(ModStructures.CAMP_PIECE.get(), tag, templateManager, location ->
            makeSettings(Rotation.NONE)
        );
    }

    private static StructurePlaceSettings makeSettings(Rotation rotation) {
        return new StructurePlaceSettings()
            .setRotation(rotation)
            .setMirror(Mirror.NONE)
            .setRotationPivot(BlockPos.ZERO)
            .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK)
            .addProcessor(new MarkerProcessor());
    }

    @Override
    protected void handleDataMarker(
        String marker,
        BlockPos pos,
        ServerLevelAccessor level,
        RandomSource random,
        BoundingBox box
    ) {
        // Handle data markers from structure blocks
        // Data markers are placed using jigsaw blocks or structure blocks with "Data" mode

        if (!(level instanceof ServerLevel serverLevel)) {
            return;
        }

        switch (marker.toLowerCase()) {
            case "kidnapper" -> {
                // Spawn a kidnapper at this position
                spawnKidnapper(serverLevel, pos, random, false);
            }
            case "kidnapper_elite" -> {
                // Spawn elite kidnapper at this position
                spawnKidnapper(serverLevel, pos, random, true);
            }
            case "slave_trader" -> {
                // Spawn slave trader (brain of camp)
                spawnSlaveTrader(serverLevel, pos, random);
            }
            case "maid" -> {
                // Spawn maid (linked to trader)
                spawnMaid(serverLevel, pos, random);
            }
            case "loot" -> {
                // TODO: Place loot chest
            }
            default -> {
                TiedUpMod.LOGGER.debug(
                    "[KidnapperCampPiece] Unknown data marker: {} at {}",
                    marker,
                    pos.toShortString()
                );
            }
        }
    }

    /**
     * Spawn a kidnapper at the given position.
     *
     * @param level The server level
     * @param pos Position to spawn at
     * @param random Random source
     * @param elite Whether to spawn an elite kidnapper
     */
    private void spawnKidnapper(
        ServerLevel level,
        BlockPos pos,
        RandomSource random,
        boolean elite
    ) {
        EntityKidnapper kidnapper;

        if (elite) {
            kidnapper = ModEntities.KIDNAPPER_ELITE.get().create(level);
        } else {
            kidnapper = ModEntities.KIDNAPPER.get().create(level);
        }

        if (kidnapper != null) {
            kidnapper.moveTo(
                pos.getX() + 0.5,
                pos.getY(),
                pos.getZ() + 0.5,
                random.nextFloat() * 360F,
                0.0F
            );
            kidnapper.finalizeSpawn(
                level,
                level.getCurrentDifficultyAt(pos),
                MobSpawnType.STRUCTURE,
                null,
                null
            );
            level.addFreshEntity(kidnapper);

            TiedUpMod.LOGGER.info(
                "[KidnapperCampPiece] Spawned {} at {} from data marker",
                elite ? "elite kidnapper" : "kidnapper",
                pos.toShortString()
            );
        }
    }

    /**
     * Spawn a slave trader at the given position.
     * Creates/updates the camp data in CampOwnership.
     *
     * @param level The server level
     * @param pos Position to spawn at
     * @param random Random source
     */
    private void spawnSlaveTrader(
        ServerLevel level,
        BlockPos pos,
        RandomSource random
    ) {
        EntitySlaveTrader trader = ModEntities.SLAVE_TRADER.get().create(level);
        if (trader == null) {
            TiedUpMod.LOGGER.warn(
                "[KidnapperCampPiece] Failed to create SlaveTrader entity"
            );
            return;
        }

        trader.moveTo(
            pos.getX() + 0.5,
            pos.getY(),
            pos.getZ() + 0.5,
            random.nextFloat() * 360F,
            0.0F
        );
        trader.finalizeSpawn(
            level,
            level.getCurrentDifficultyAt(pos),
            MobSpawnType.STRUCTURE,
            null,
            null
        );

        // Create or get the camp in CampOwnership registry
        CampOwnership registry = CampOwnership.get(level);
        CampOwnership.CampData camp = registry.findNearestAliveCamp(pos, 50);

        if (camp == null) {
            // Create a new camp
            UUID campId = UUID.randomUUID();
            registry.registerCamp(campId, trader.getUUID(), null, pos);
            camp = registry.getCamp(campId);
        } else {
            // Update existing camp with this trader
            camp.setTraderUUID(trader.getUUID());
            camp.setCenter(pos);
            registry.setDirty();
        }

        // Link trader to camp
        if (camp != null) {
            trader.setCampUUID(camp.getCampId());
            this.currentCamp = camp;
        }

        // Store for maid linking
        this.spawnedTraderUUID = trader.getUUID();

        level.addFreshEntity(trader);

        TiedUpMod.LOGGER.info(
            "[KidnapperCampPiece] Spawned slave trader {} at {} from data marker, camp={}",
            trader.getNpcName(),
            pos.toShortString(),
            camp != null ? camp.getCampId().toString().substring(0, 8) : "null"
        );
    }

    /**
     * Spawn a maid at the given position.
     * Must be called AFTER trader spawns to establish link.
     *
     * @param level The server level
     * @param pos Position to spawn at
     * @param random Random source
     */
    private void spawnMaid(
        ServerLevel level,
        BlockPos pos,
        RandomSource random
    ) {
        EntityMaid maid = ModEntities.MAID.get().create(level);
        if (maid == null) {
            TiedUpMod.LOGGER.warn(
                "[KidnapperCampPiece] Failed to create Maid entity"
            );
            return;
        }

        maid.moveTo(
            pos.getX() + 0.5,
            pos.getY(),
            pos.getZ() + 0.5,
            random.nextFloat() * 360F,
            0.0F
        );
        maid.finalizeSpawn(
            level,
            level.getCurrentDifficultyAt(pos),
            MobSpawnType.STRUCTURE,
            null,
            null
        );

        // Link to trader (must be spawned after trader)
        boolean linkedSuccessfully = false;

        if (this.spawnedTraderUUID != null) {
            maid.setMasterTraderUUID(this.spawnedTraderUUID);

            // Find the trader entity and update its maid reference
            Entity traderEntity = level.getEntity(this.spawnedTraderUUID);
            if (traderEntity instanceof EntitySlaveTrader trader) {
                trader.setMaidUUID(maid.getUUID());
                linkedSuccessfully = true;

                TiedUpMod.LOGGER.debug(
                    "[KidnapperCampPiece] Linked maid {} to trader {}",
                    maid.getNpcName(),
                    trader.getNpcName()
                );
            }

            // Link maid to camp
            if (this.currentCamp != null) {
                this.currentCamp.setMaidUUID(maid.getUUID());
                CampOwnership.get(level).setDirty();
            }
        }

        // Fallback: If no trader link established, search for nearby trader/camp
        if (!linkedSuccessfully) {
            TiedUpMod.LOGGER.debug(
                "[KidnapperCampPiece] Maid at {} - no direct trader link, searching nearby...",
                pos.toShortString()
            );

            // Try to find a nearby camp first
            CampOwnership registry = CampOwnership.get(level);
            CampOwnership.CampData camp = registry.findNearestAliveCamp(
                pos,
                50
            );

            if (camp != null && camp.getTraderUUID() != null) {
                // Found a camp with a trader - link to it
                maid.setMasterTraderUUID(camp.getTraderUUID());
                camp.setMaidUUID(maid.getUUID());
                registry.setDirty();

                // Try to update the trader entity too
                Entity traderEntity = level.getEntity(camp.getTraderUUID());
                if (traderEntity instanceof EntitySlaveTrader trader) {
                    trader.setMaidUUID(maid.getUUID());
                    linkedSuccessfully = true;

                    TiedUpMod.LOGGER.info(
                        "[KidnapperCampPiece] Maid {} linked to trader {} via camp fallback",
                        maid.getNpcName(),
                        trader.getNpcName()
                    );
                }
            }

            // Last resort: search for nearby trader entities
            if (!linkedSuccessfully) {
                java.util.List<EntitySlaveTrader> nearbyTraders =
                    level.getEntitiesOfClass(
                        EntitySlaveTrader.class,
                        maid.getBoundingBox().inflate(50)
                    );

                if (!nearbyTraders.isEmpty()) {
                    EntitySlaveTrader trader = nearbyTraders.get(0);
                    maid.setMasterTraderUUID(trader.getUUID());
                    trader.setMaidUUID(maid.getUUID());
                    linkedSuccessfully = true;

                    // Update camp if trader has one
                    if (trader.getCampUUID() != null) {
                        CampOwnership.CampData traderCamp = registry.getCamp(
                            trader.getCampUUID()
                        );
                        if (traderCamp != null) {
                            traderCamp.setMaidUUID(maid.getUUID());
                            registry.setDirty();
                        }
                    }

                    TiedUpMod.LOGGER.info(
                        "[KidnapperCampPiece] Maid {} linked to nearby trader {} via entity search fallback",
                        maid.getNpcName(),
                        trader.getNpcName()
                    );
                }
            }
        }

        if (!linkedSuccessfully) {
            TiedUpMod.LOGGER.warn(
                "[KidnapperCampPiece] Maid spawned at {} but no trader found! Maid will be orphaned.",
                pos.toShortString()
            );
        }

        level.addFreshEntity(maid);

        TiedUpMod.LOGGER.info(
            "[KidnapperCampPiece] Spawned maid {} at {} from data marker, linked to trader={}",
            maid.getNpcName(),
            pos.toShortString(),
            this.spawnedTraderUUID != null
                ? this.spawnedTraderUUID.toString().substring(0, 8)
                : "null"
        );
    }
}
