package com.tiedup.remake.entities.ai.personality;

import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.entities.NpcFishingBobber;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.PersonalityState;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.Vec3;

/**
 * AI Goal: NPC fishes near water blocks close to chest hub using a fishing bobber entity.
 *
 * <p>Requires a fishing rod in the NPC's main hand. Uses the chest-hub system
 * (commandTarget = chest position, work zone around it).</p>
 *
 * <p>Job-specific phases: MOVING_TO_WATER, CASTING, WAITING, REELING.</p>
 */
public class NpcFishCommandGoal extends AbstractNpcJobGoal {

    private static final double INTERACT_DISTANCE = 2.5;
    private static final float WORK_INTENSITY = 0.5f;

    // Job-specific phases
    private static final int PHASE_MOVING_TO_WATER = PHASE_JOB_SPECIFIC;
    private static final int PHASE_CASTING = PHASE_JOB_SPECIFIC + 1;
    private static final int PHASE_WAITING = PHASE_JOB_SPECIFIC + 2;
    private static final int PHASE_REELING = PHASE_JOB_SPECIFIC + 3;

    @Nullable
    private BlockPos waterPos;

    @Nullable
    private NpcFishingBobber activeBobber;

    private int castDelay;

    public NpcFishCommandGoal(EntityDamsel npc, double speedModifier) {
        super(npc, speedModifier);
    }

    // ==================== Abstract Implementations ====================

    @Override
    protected NpcCommand getCommand() {
        return NpcCommand.FISH;
    }

    @Override
    protected boolean extraCanUse() {
        return npc.getMainHandItem().is(Items.FISHING_ROD);
    }

    @Override
    protected boolean extraCanContinueToUse() {
        return npc.getMainHandItem().is(Items.FISHING_ROD);
    }

    @Override
    @Nullable
    protected BlockPos findWorkTarget() {
        return findWaterBlock();
    }

    @Override
    protected void onWorkTargetFound(BlockPos target) {
        this.waterPos = target;
        this.phase = PHASE_MOVING_TO_WATER;
    }

    @Override
    protected void onStart() {
        this.castDelay = 0;
        this.waterPos = null;
        this.activeBobber = null;
    }

    @Override
    protected void onStop() {
        discardBobber();
        this.waterPos = null;
    }

    @Override
    protected double getInteractDistance() {
        return INTERACT_DISTANCE;
    }

    @Override
    protected void tickJobSpecific() {
        switch (this.phase) {
            case PHASE_MOVING_TO_WATER -> tickMovingToWater();
            case PHASE_CASTING -> tickCasting();
            case PHASE_WAITING -> tickWaiting();
            case PHASE_REELING -> tickReeling();
        }
    }

    // ==================== Fish-Specific Phase Ticks ====================

    private void tickMovingToWater() {
        if (this.waterPos == null) {
            this.phase = PHASE_SCANNING;
            return;
        }

        // Move to a block adjacent to water (not into it)
        if (--this.pathRecalcTimer <= 0) {
            this.pathRecalcTimer = 10;
            BlockPos adjacentPos = findAdjacentSolidBlock(waterPos);
            if (adjacentPos != null) {
                npc
                    .getNavigation()
                    .moveTo(
                        adjacentPos.getX() + 0.5,
                        adjacentPos.getY(),
                        adjacentPos.getZ() + 0.5,
                        speedModifier
                    );
            } else {
                npc
                    .getNavigation()
                    .moveTo(
                        waterPos.getX() + 0.5,
                        waterPos.getY() + 1,
                        waterPos.getZ() + 0.5,
                        speedModifier
                    );
            }
        }

        double dist = npc.blockPosition().distSqr(waterPos);
        if (dist <= INTERACT_DISTANCE * INTERACT_DISTANCE) {
            npc.getNavigation().stop();
            this.phase = PHASE_CASTING;
            this.castDelay = 10;
        }
    }

    private void tickCasting() {
        if (this.waterPos == null) {
            this.phase = PHASE_SCANNING;
            return;
        }

        // Look at water while preparing to cast
        npc
            .getLookControl()
            .setLookAt(
                waterPos.getX() + 0.5,
                waterPos.getY() + 0.5,
                waterPos.getZ() + 0.5
            );

        if (--this.castDelay > 0) {
            return;
        }

        // Swing arm (cast animation)
        npc.swing(InteractionHand.MAIN_HAND);

        // Spawn bobber
        Level level = npc.level();
        NpcFishingBobber bobber = NpcFishingBobber.create(level, npc, waterPos);
        level.addFreshEntity(bobber);
        this.activeBobber = bobber;

        // Cast sound
        level.playSound(
            null,
            npc.getX(),
            npc.getY(),
            npc.getZ(),
            SoundEvents.FISHING_BOBBER_THROW,
            SoundSource.NEUTRAL,
            0.5f,
            0.4f / (npc.getRandom().nextFloat() * 0.4f + 0.8f)
        );

        this.phase = PHASE_WAITING;
    }

    private void tickWaiting() {
        if (activeBobber == null || !activeBobber.isAlive()) {
            this.activeBobber = null;
            this.phase = PHASE_SCANNING;
            this.scanTimer = 0;
            return;
        }

        // Look at bobber while waiting
        npc
            .getLookControl()
            .setLookAt(
                activeBobber.getX(),
                activeBobber.getY(),
                activeBobber.getZ()
            );

        if (activeBobber.isBiting()) {
            this.phase = PHASE_REELING;
        }
    }

    private void tickReeling() {
        // Swing arm (reel animation)
        npc.swing(InteractionHand.MAIN_HAND);

        // Discard bobber
        discardBobber();

        // Generate loot from fishing loot table
        Level level = npc.level();
        List<ItemStack> loot = generateFishingLoot(level);
        for (ItemStack item : loot) {
            heldItems.add(item);
        }

        // Apply yield bonus
        float yieldMult = NpcGoalHelper.getJobYieldMultiplier(
            npc,
            NpcCommand.FISH
        );
        if (
            yieldMult > 1.0f && npc.getRandom().nextFloat() < (yieldMult - 1.0f)
        ) {
            if (!loot.isEmpty()) {
                ItemStack bonusDrop = loot
                    .get(npc.getRandom().nextInt(loot.size()))
                    .copy();
                bonusDrop.setCount(1);
                heldItems.add(bonusDrop);
            }
        }

        // Reel sound
        level.playSound(
            null,
            npc.getX(),
            npc.getY(),
            npc.getZ(),
            SoundEvents.FISHING_BOBBER_RETRIEVE,
            SoundSource.NEUTRAL,
            1.0f,
            1.0f
        );

        // Drain rest
        PersonalityState personalityState = npc.getPersonalityState();
        if (personalityState != null) {
            personalityState.getNeeds().drainFromWork(WORK_INTENSITY);
        }

        // Check if inventory full -> store, otherwise fish again
        if (heldItems.size() >= MAX_HELD_ITEMS) {
            this.phase = PHASE_STORING;
        } else {
            this.phase = PHASE_SCANNING;
            this.scanTimer = 0;
        }
    }

    // ==================== Fish-Specific Utility ====================

    private void discardBobber() {
        if (activeBobber != null) {
            if (activeBobber.isAlive()) {
                activeBobber.discard();
            }
            activeBobber = null;
        }
    }

    private List<ItemStack> generateFishingLoot(Level level) {
        if (!(level instanceof ServerLevel serverLevel)) {
            return generateFallbackLoot();
        }

        try {
            LootTable lootTable = serverLevel
                .getServer()
                .getLootData()
                .getLootTable(BuiltInLootTables.FISHING);

            float experienceLuck =
                NpcGoalHelper.getJobYieldMultiplier(npc, NpcCommand.FISH) -
                1.0f;

            LootParams params = new LootParams.Builder(serverLevel)
                .withParameter(
                    LootContextParams.ORIGIN,
                    new Vec3(npc.getX(), npc.getY(), npc.getZ())
                )
                .withParameter(LootContextParams.THIS_ENTITY, npc)
                .withLuck(experienceLuck)
                .create(LootContextParamSets.FISHING);

            return lootTable.getRandomItems(params);
        } catch (Exception e) {
            return generateFallbackLoot();
        }
    }

    private List<ItemStack> generateFallbackLoot() {
        List<ItemStack> loot = new ArrayList<>();
        float roll = npc.getRandom().nextFloat();

        if (roll < 0.50f) {
            loot.add(new ItemStack(Items.COD));
        } else if (roll < 0.75f) {
            loot.add(new ItemStack(Items.SALMON));
        } else if (roll < 0.88f) {
            loot.add(new ItemStack(Items.TROPICAL_FISH));
        } else if (roll < 0.95f) {
            loot.add(new ItemStack(Items.PUFFERFISH));
        } else {
            float rare = npc.getRandom().nextFloat();
            if (rare < 0.3f) {
                loot.add(new ItemStack(Items.BOW));
            } else if (rare < 0.6f) {
                loot.add(new ItemStack(Items.FISHING_ROD));
            } else if (rare < 0.8f) {
                loot.add(new ItemStack(Items.SADDLE));
            } else {
                loot.add(new ItemStack(Items.NAME_TAG));
            }
        }

        return loot;
    }

    @Nullable
    private BlockPos findWaterBlock() {
        if (this.chestPos == null) return null;

        Level level = npc.level();
        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();

        BlockPos nearest = null;
        double nearestDist = Double.MAX_VALUE;

        for (int x = -WORK_RADIUS; x <= WORK_RADIUS; x++) {
            for (int z = -WORK_RADIUS; z <= WORK_RADIUS; z++) {
                for (int y = -2; y <= 2; y++) {
                    mutable.set(
                        chestPos.getX() + x,
                        chestPos.getY() + y,
                        chestPos.getZ() + z
                    );

                    if (
                        level.getBlockState(mutable).getBlock() == Blocks.WATER
                    ) {
                        double dist = npc.blockPosition().distSqr(mutable);
                        if (dist < nearestDist) {
                            nearest = mutable.immutable();
                            nearestDist = dist;
                        }
                    }
                }
            }
        }

        return nearest;
    }

    @Nullable
    private BlockPos findAdjacentSolidBlock(BlockPos water) {
        Level level = npc.level();
        BlockPos[] offsets = {
            water.north(),
            water.south(),
            water.east(),
            water.west(),
        };

        for (BlockPos adjacent : offsets) {
            if (
                level.getBlockState(adjacent).isAir() &&
                level
                    .getBlockState(adjacent.below())
                    .isSolidRender(level, adjacent.below())
            ) {
                return adjacent;
            }
        }

        return null;
    }
}
