package com.tiedup.remake.entities.maid.components;

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.entities.ai.maid.MaidState;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.phys.AABB;

/**
 * Manages the bidirectional link between an EntityMaid and her master EntitySlaveTrader.
 *
 * Responsibilities:
 * - Storing and persisting the master trader UUID
 * - Multi-level trader lookup (cache, UUID, camp ownership, area search)
 * - Periodic link establishment for maids spawned before their trader
 * - Camp UUID lookup via CampOwnership registry
 *
 * This is a plain Java component (not a Forge capability or system).
 * Lifecycle is owned by EntityMaid.
 */
public class MaidTraderLink {

    // CONSTANTS

    /** Search interval in ticks (2 seconds) */
    private static final int TRADER_SEARCH_INTERVAL = 40;

    /** Interval for link establishment retry (5 seconds) */
    private static final int LINK_ESTABLISHMENT_INTERVAL = 100;

    /** Maximum attempts to establish link before giving up */
    private static final int MAX_LINK_ATTEMPTS = 12; // 1 minute total

    // STATE

    private final EntityMaid maid;

    /** UUID of the trader this maid serves */
    @Nullable
    private UUID masterTraderUUID;

    /** Cached trader reference for performance */
    @Nullable
    private transient EntitySlaveTrader cachedTrader;

    /** Tick counter for periodic trader search */
    private transient int traderSearchCooldown = 0;

    /** Tick counter for periodic link establishment (when masterTraderUUID is null) */
    private transient int linkEstablishmentCounter = 0;

    /** Current link attempt count */
    private transient int linkAttemptCount = 0;

    // CONSTRUCTOR

    public MaidTraderLink(EntityMaid maid) {
        this.maid = maid;
    }

    // ACCESSORS

    @Nullable
    public UUID getMasterTraderUUID() {
        return masterTraderUUID;
    }

    public void setMasterTraderUUID(@Nullable UUID masterTraderUUID) {
        this.masterTraderUUID = masterTraderUUID;
    }

    /**
     * Clear the trader UUID and cached reference.
     * Used when the trader dies or the maid is captured and removed from camp.
     */
    public void clearTraderUUID() {
        this.masterTraderUUID = null;
        this.cachedTrader = null;
    }

    // TRADER LOOKUP

    /**
     * Get the master trader entity.
     * Uses multi-level lookup strategy:
     * 1. Cached reference (if still valid)
     * 2. Direct entity lookup by UUID
     * 3. CampOwnership registry lookup
     * 4. Area search around camp center
     * 5. Area search around maid position
     */
    @Nullable
    public EntitySlaveTrader getMasterTrader() {
        if (!(maid.level() instanceof ServerLevel serverLevel)) {
            return null;
        }

        // Check cached trader first
        if (
            cachedTrader != null &&
            cachedTrader.isAlive() &&
            !cachedTrader.isRemoved()
        ) {
            // Update masterTraderUUID if cache is valid but UUID was null
            if (masterTraderUUID == null) {
                masterTraderUUID = cachedTrader.getUUID();
            }
            return cachedTrader;
        }
        cachedTrader = null;

        // If no UUID and no cache, search via camp ownership
        if (masterTraderUUID == null) {
            return searchTraderViaCampOwnership(serverLevel);
        }

        // Try direct entity lookup (fast path - works if same chunk)
        var entity = serverLevel.getEntity(masterTraderUUID);
        if (entity instanceof EntitySlaveTrader trader && trader.isAlive()) {
            cachedTrader = trader;
            return trader;
        }

        // Fallback: search via CampOwnership and area search
        return searchTraderViaCampOwnership(serverLevel);
    }

    // CAMP UUID

    /**
     * Get the camp UUID this maid belongs to.
     * Looks up via CampOwnership registry using maid's UUID.
     */
    @Nullable
    public UUID getCampUUID() {
        if (!(maid.level() instanceof ServerLevel serverLevel)) {
            return null;
        }
        CampOwnership ownership = CampOwnership.get(serverLevel);
        CampOwnership.CampData camp = ownership.getCampByMaid(maid.getUUID());
        return camp != null ? camp.getCampId() : null;
    }

    // SEARCH STRATEGIES

    /**
     * Search for trader using CampOwnership registry and area search.
     */
    @Nullable
    private EntitySlaveTrader searchTraderViaCampOwnership(
        ServerLevel serverLevel
    ) {
        // Only search periodically to avoid performance issues
        if (traderSearchCooldown > 0) {
            traderSearchCooldown--;
            return null;
        }
        traderSearchCooldown = TRADER_SEARCH_INTERVAL;

        CampOwnership ownership = CampOwnership.get(serverLevel);

        // Try to find camp by this maid's UUID
        CampOwnership.CampData camp = ownership.getCampByMaid(maid.getUUID());

        if (camp != null && camp.isAlive()) {
            UUID traderUUID = camp.getTraderUUID();
            if (traderUUID != null) {
                // Update our stored UUID
                this.masterTraderUUID = traderUUID;

                // Search around camp center (most likely location)
                BlockPos campCenter = camp.getCenter();
                if (campCenter != null) {
                    EntitySlaveTrader trader = searchTraderInArea(
                        serverLevel,
                        traderUUID,
                        campCenter,
                        EntityMaid.MAID_FOLLOW_RANGE
                    );
                    if (trader != null) {
                        cachedTrader = trader;
                        return trader;
                    }
                }
            }
        }

        // Last fallback: search around maid's position
        if (masterTraderUUID != null) {
            EntitySlaveTrader trader = searchTraderInArea(
                serverLevel,
                masterTraderUUID,
                maid.blockPosition(),
                EntityMaid.MAID_FOLLOW_RANGE
            );
            if (trader != null) {
                cachedTrader = trader;
                return trader;
            }
        }

        return null;
    }

    /**
     * Search for a specific trader in an area around a position.
     */
    @Nullable
    private EntitySlaveTrader searchTraderInArea(
        ServerLevel level,
        UUID targetUUID,
        BlockPos center,
        double radius
    ) {
        AABB searchBox = new AABB(center).inflate(radius);

        for (EntitySlaveTrader trader : level.getEntitiesOfClass(
            EntitySlaveTrader.class,
            searchBox
        )) {
            if (trader.getUUID().equals(targetUUID) && trader.isAlive()) {
                return trader;
            }
        }
        return null;
    }

    // LINK ESTABLISHMENT

    /**
     * Periodically attempt to establish link to master trader if not linked.
     * This handles the case where maid spawns before trader in structure generation.
     */
    public void tickLinkEstablishment(ServerLevel serverLevel) {
        // Skip if already linked
        if (masterTraderUUID != null) {
            return;
        }

        // Skip if freed (trader died)
        if (maid.getMaidState() == MaidState.FREE) {
            return;
        }

        // Skip if max attempts exceeded (prevents infinite searching)
        if (linkAttemptCount >= MAX_LINK_ATTEMPTS) {
            return;
        }

        // Increment counter and check interval
        linkEstablishmentCounter++;
        if (linkEstablishmentCounter < LINK_ESTABLISHMENT_INTERVAL) {
            return;
        }
        linkEstablishmentCounter = 0;
        linkAttemptCount++;

        TiedUpMod.LOGGER.debug(
            "[EntityMaid] {} attempting to establish trader link (attempt {}/{})",
            maid.getNpcName(),
            linkAttemptCount,
            MAX_LINK_ATTEMPTS
        );

        // Strategy 1: Search via CampOwnership for camps near our position
        CampOwnership ownership = CampOwnership.get(serverLevel);
        CampOwnership.CampData camp = ownership.findNearestAliveCamp(
            maid.blockPosition(),
            50
        );

        if (camp != null && camp.getTraderUUID() != null) {
            // Found a camp with a trader - try to link
            UUID traderUUID = camp.getTraderUUID();

            // Search for the trader entity
            EntitySlaveTrader trader = findTraderEntity(
                serverLevel,
                traderUUID
            );
            if (trader != null) {
                establishBidirectionalLink(trader, camp, ownership);
                return;
            }
        }

        // Strategy 2: Search for any nearby trader without a maid
        EntitySlaveTrader unlinkedTrader = findUnlinkedTraderNearby(
            serverLevel
        );
        if (unlinkedTrader != null) {
            // Get or create camp data
            CampOwnership.CampData traderCamp = null;
            if (unlinkedTrader.getCampUUID() != null) {
                traderCamp = ownership.getCamp(unlinkedTrader.getCampUUID());
            }
            establishBidirectionalLink(unlinkedTrader, traderCamp, ownership);
        }
    }

    /**
     * Find a trader entity by UUID, searching around our position.
     */
    @Nullable
    private EntitySlaveTrader findTraderEntity(
        ServerLevel level,
        UUID traderUUID
    ) {
        // Direct lookup first
        var entity = level.getEntity(traderUUID);
        if (entity instanceof EntitySlaveTrader trader && trader.isAlive()) {
            return trader;
        }

        // Area search around our position
        return searchTraderInArea(
            level,
            traderUUID,
            maid.blockPosition(),
            EntityMaid.MAID_FOLLOW_RANGE
        );
    }

    /**
     * Find any nearby trader that doesn't have a maid assigned.
     */
    @Nullable
    private EntitySlaveTrader findUnlinkedTraderNearby(ServerLevel level) {
        AABB searchBox = new AABB(maid.blockPosition()).inflate(
            EntityMaid.MAID_FOLLOW_RANGE
        );

        for (EntitySlaveTrader trader : level.getEntitiesOfClass(
            EntitySlaveTrader.class,
            searchBox
        )) {
            if (trader.isAlive() && trader.getMaidUUID() == null) {
                return trader;
            }
        }
        return null;
    }

    /**
     * Establish bidirectional link between this maid and a trader.
     */
    private void establishBidirectionalLink(
        EntitySlaveTrader trader,
        @Nullable CampOwnership.CampData camp,
        CampOwnership ownership
    ) {
        // Link maid -> trader
        this.masterTraderUUID = trader.getUUID();
        this.cachedTrader = trader;

        // Link trader -> maid
        trader.setMaidUUID(maid.getUUID());

        // Link camp -> maid (if camp exists)
        if (camp != null) {
            camp.setMaidUUID(maid.getUUID());
            ownership.setDirty();
        }

        // Reset counters
        this.linkAttemptCount = 0;
        this.linkEstablishmentCounter = 0;

        TiedUpMod.LOGGER.info(
            "[EntityMaid] {} successfully linked to trader {} via periodic search",
            maid.getNpcName(),
            trader.getNpcName()
        );
    }

    // NBT PERSISTENCE

    /**
     * Save trader link data to NBT.
     */
    public void save(CompoundTag tag) {
        if (masterTraderUUID != null) {
            tag.putUUID("MasterTraderUUID", masterTraderUUID);
        }
    }

    /**
     * Load trader link data from NBT.
     */
    public void load(CompoundTag tag) {
        if (tag.contains("MasterTraderUUID")) {
            masterTraderUUID = tag.getUUID("MasterTraderUUID");
        }
    }
}
