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

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityKidnapperArcher;
import com.tiedup.remake.entities.EntityRopeArrow;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import java.util.EnumSet;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.phys.Vec3;

/**
 * AI Goal for EntityKidnapperArcher to attack targets from range.
 *
 *
 * This goal:
 * 1. Finds targets within bow range (10-25 blocks)
 * 2. Maintains distance from target (strafes if too close)
 * 3. Shoots rope arrows to tie up target
 * 4. Stops when target is tied (KidnapperCaptureGoal takes over)
 *
 * Based on Minecraft's RangedBowAttackGoal but adapted for rope arrows.
 */
public class KidnapperArcherRangedGoal extends Goal {

    private final EntityKidnapperArcher archer;

    /** Minimum distance to target (too close = strafe away) */
    private static final double MIN_ATTACK_DISTANCE = 8.0D;

    /** Maximum distance to target (too far = move closer) */
    private static final double MAX_ATTACK_DISTANCE = 25.0D;

    /** Ideal distance to maintain */
    private static final double IDEAL_DISTANCE = 15.0D;

    /** Ticks between shots */
    private static final int ATTACK_COOLDOWN = 20; // 1 second (reduced from 2s)

    /** Time to aim before shooting */
    private static final int AIM_TIME = 20; // 1 second

    /** Current attack timer */
    private int attackTimer;

    /** Combat duration tracker for timeout */
    private int combatTicks;

    /** Strafing direction (1 = right, -1 = left) */
    private int strafeDirection;

    /** Time until strafe direction changes */
    private int strafeTimer;

    /** Whether we're currently backing away */
    private boolean backingAway;

    /** Speed multiplier when strafing */
    private static final double STRAFE_SPEED = 0.8D;

    /** Speed multiplier when repositioning */
    private static final double REPOSITION_SPEED = 1.0D;

    /** Current target */
    private LivingEntity target;

    /** Dialogue cooldown */
    private int dialogueCooldown;
    private static final int DIALOGUE_INTERVAL = 200; // 10 seconds

    /** Maximum combat duration before giving up (60 seconds) */
    private static final int MAX_COMBAT_TICKS = 1200;

    /** Whether we're currently aiming */
    private boolean isAiming;

    /** Ticks without line of sight (for canContinueToUse timeout) */
    private int noLosTimer;

    /**
     * Create a new archer ranged attack goal.
     *
     * @param archer The archer kidnapper
     */
    public KidnapperArcherRangedGoal(EntityKidnapperArcher archer) {
        this.archer = archer;
        this.attackTimer = 0;
        this.strafeDirection = 1;
        this.strafeTimer = 0;
        this.backingAway = false;
        this.dialogueCooldown = 0;
        this.isAiming = false;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
    }

    @Override
    public boolean canUse() {
        // Must have a target
        LivingEntity currentTarget = this.archer.getTarget();
        if (currentTarget == null || !currentTarget.isAlive()) {
            return false;
        }

        // Can't attack if tied up
        if (this.archer.isTiedUp()) {
            return false;
        }

        // Can't attack if already have a captive
        if (this.archer.hasCaptives()) {
            return false;
        }

        // Check if target is already tied (let capture goal handle it)
        IBondageState targetState = KidnappedHelper.getKidnappedState(
            currentTarget
        );
        if (targetState != null && targetState.isTiedUp()) {
            return false;
        }

        // Must be in valid range
        double distSq = this.archer.distanceToSqr(currentTarget);
        if (distSq > MAX_ATTACK_DISTANCE * MAX_ATTACK_DISTANCE) {
            return false;
        }

        // Reject targets too far vertically (prevents targeting through floors)
        double yDiff = Math.abs(this.archer.getY() - currentTarget.getY());
        if (yDiff > 10.0) {
            return false;
        }

        // Must have line of sight (prevents shooting through walls/ground)
        if (!this.archer.getSensing().hasLineOfSight(currentTarget)) {
            return false;
        }

        this.target = currentTarget;
        return true;
    }

    @Override
    public boolean canContinueToUse() {
        // Stop if no target
        if (this.target == null || !this.target.isAlive()) {
            return false;
        }

        // Stop if tied up
        if (this.archer.isTiedUp()) {
            return false;
        }

        // Stop if got a captive
        if (this.archer.hasCaptives()) {
            return false;
        }

        // Stop if target is now tied (success! capture goal takes over)
        IBondageState targetState = KidnappedHelper.getKidnappedState(
            this.target
        );
        if (targetState != null && targetState.isTiedUp()) {
            TiedUpMod.LOGGER.info(
                "[KidnapperArcherRangedGoal] {} tied up {} with arrow, switching to capture",
                this.archer.getNpcName(),
                this.target.getName().getString()
            );
            return false;
        }

        // Stop if lost line of sight for too long (prevents shooting through terrain)
        if (!this.archer.getSensing().hasLineOfSight(this.target)) {
            this.noLosTimer++;
            if (this.noLosTimer > 60) {
                // 3 seconds without LOS
                return false;
            }
        } else {
            this.noLosTimer = 0;
        }

        return true;
    }

    @Override
    public void start() {
        this.attackTimer = 0;
        this.combatTicks = 0;
        this.noLosTimer = 0;
        this.strafeTimer = 0;
        this.strafeDirection = this.archer.getRandom().nextBoolean() ? 1 : -1;
        this.backingAway = false;

        // Enter ranged mode - enables bow animation
        this.archer.setInRangedMode(true);

        TiedUpMod.LOGGER.debug(
            "[KidnapperArcherRangedGoal] {} starting ranged attack on {}",
            this.archer.getNpcName(),
            this.target.getName().getString()
        );
    }

    @Override
    public void stop() {
        // Save target reference before anything else
        LivingEntity savedTarget = this.target;

        TiedUpMod.LOGGER.debug(
            "[KidnapperArcherRangedGoal] {} stopping ranged goal, target was: {}",
            this.archer.getNpcName(),
            savedTarget != null ? savedTarget.getName().getString() : "null"
        );

        // Check if target was successfully tied - if so, we need to restore the archer's target
        // (it may have been cleared by another goal like FindTargetGoal)
        if (savedTarget != null && savedTarget.isAlive()) {
            IBondageState targetState = KidnappedHelper.getKidnappedState(
                savedTarget
            );
            if (targetState != null && targetState.isTiedUp()) {
                // Target was successfully tied! Make sure archer has target for CaptureGoal
                if (this.archer.getTarget() == null) {
                    TiedUpMod.LOGGER.debug(
                        "[KidnapperArcherRangedGoal] {} restoring target {} for CaptureGoal",
                        this.archer.getNpcName(),
                        savedTarget.getName().getString()
                    );
                    this.archer.setTarget(savedTarget);
                }
            }
        }

        this.target = null;
        this.attackTimer = 0;

        // Always stop aiming animation and ranged mode when goal stops
        this.isAiming = false;
        this.archer.setAiming(false);
        this.archer.setInRangedMode(false);

        TiedUpMod.LOGGER.debug(
            "[KidnapperArcherRangedGoal] {} archer target after stop: {}",
            this.archer.getNpcName(),
            this.archer.getTarget() != null
                ? this.archer.getTarget().getName().getString()
                : "null"
        );
    }

    @Override
    public void tick() {
        if (this.target == null) return;

        // TIMEOUT: Give up combat after MAX_COMBAT_TICKS to prevent shooting walls forever
        this.combatTicks++;
        if (this.combatTicks > MAX_COMBAT_TICKS) {
            TiedUpMod.LOGGER.info(
                "[KidnapperArcherRangedGoal] {} combat timeout ({} ticks), losing interest in {}",
                this.archer.getNpcName(),
                MAX_COMBAT_TICKS,
                this.target.getName().getString()
            );
            this.archer.setTarget(null);
            return;
        }

        // Always look at target with high rotation speeds for better tracking
        // Parameters: maxYawSpeed (horizontal), maxPitchSpeed (vertical)
        // Higher values = faster tracking, more responsive aiming
        this.archer.getLookControl().setLookAt(this.target, 180.0F, 90.0F);

        double distSq = this.archer.distanceToSqr(this.target);
        double dist = Math.sqrt(distSq);

        // Movement logic
        handleMovement(dist);

        // Attack logic
        handleAttack(dist);

        // Dialogue
        if (this.dialogueCooldown > 0) {
            this.dialogueCooldown--;
        }
    }

    /**
     * Handle movement: maintain ideal distance, strafe to avoid attacks.
     */
    private void handleMovement(double dist) {
        // Too close - back away
        if (dist < MIN_ATTACK_DISTANCE) {
            this.backingAway = true;
            Vec3 awayDir = this.archer.position()
                .subtract(this.target.position())
                .normalize();
            double targetX = this.archer.getX() + awayDir.x * 5;
            double targetZ = this.archer.getZ() + awayDir.z * 5;
            this.archer.getNavigation().moveTo(
                targetX,
                this.archer.getY(),
                targetZ,
                REPOSITION_SPEED
            );
            return;
        }

        this.backingAway = false;

        // Too far - move closer
        if (dist > MAX_ATTACK_DISTANCE) {
            this.archer.getNavigation().moveTo(this.target, REPOSITION_SPEED);
            return;
        }

        // In range - strafe
        this.strafeTimer--;
        if (this.strafeTimer <= 0) {
            // Change strafe direction randomly
            this.strafeDirection = this.archer.getRandom().nextBoolean()
                ? 1
                : -1;
            this.strafeTimer = 40 + this.archer.getRandom().nextInt(40);
        }

        // Strafe perpendicular to target
        Vec3 toTarget = this.target.position()
            .subtract(this.archer.position())
            .normalize();
        Vec3 strafeDir = new Vec3(
            -toTarget.z * this.strafeDirection,
            0,
            toTarget.x * this.strafeDirection
        );

        double strafeX = this.archer.getX() + strafeDir.x * 3;
        double strafeZ = this.archer.getZ() + strafeDir.z * 3;

        // Also adjust distance slightly
        if (dist < IDEAL_DISTANCE - 2) {
            strafeX -= toTarget.x * 2;
            strafeZ -= toTarget.z * 2;
        } else if (dist > IDEAL_DISTANCE + 2) {
            strafeX += toTarget.x * 2;
            strafeZ += toTarget.z * 2;
        }

        this.archer.getNavigation().moveTo(
            strafeX,
            this.archer.getY(),
            strafeZ,
            STRAFE_SPEED
        );
    }

    /**
     * Handle attack: aim and shoot rope arrows.
     */
    private void handleAttack(double dist) {
        // Don't attack while backing away
        if (this.backingAway) {
            this.attackTimer = 0;
            // Stop aiming if backing away
            if (this.isAiming) {
                this.isAiming = false;
                this.archer.setAiming(false);
            }
            return;
        }

        // Must be in range
        if (dist < MIN_ATTACK_DISTANCE || dist > MAX_ATTACK_DISTANCE) {
            // Stop aiming if out of range
            if (this.isAiming) {
                this.isAiming = false;
                this.archer.setAiming(false);
            }
            return;
        }

        this.attackTimer++;

        // Start aiming at the beginning
        if (this.attackTimer == 1 && !this.isAiming) {
            this.isAiming = true;
            this.archer.setAiming(true);
        }

        // While aiming, face the target directly (body rotation)
        if (this.isAiming && this.target != null) {
            double dx = this.target.getX() - this.archer.getX();
            double dz = this.target.getZ() - this.archer.getZ();
            float targetYaw =
                (float) (Math.atan2(dz, dx) * (180.0 / Math.PI)) - 90.0F;
            this.archer.setYRot(targetYaw);
            this.archer.yBodyRot = targetYaw;
        }

        // Shoot when aim time is reached
        if (this.attackTimer == AIM_TIME) {
            shootArrow();

            // Stop aiming after shooting
            this.isAiming = false;
            this.archer.setAiming(false);

            // Dialogue on first shot
            if (this.dialogueCooldown <= 0) {
                this.archer.talkToPlayersInRadius(
                    DialogueCategory.CAPTURE_START,
                    20
                );
                this.dialogueCooldown = DIALOGUE_INTERVAL;
            }
        }

        // Reset timer after full cycle (aim + cooldown)
        if (this.attackTimer >= AIM_TIME + ATTACK_COOLDOWN) {
            this.attackTimer = 0;
        }
    }

    /**
     * Shoot a rope arrow at the target.
     * Only executes on server side (where AI goals run).
     */
    private void shootArrow() {
        if (this.target == null) return;

        // Safety check: AI goals should only run server-side, but verify
        if (this.archer.level().isClientSide) {
            return;
        }

        // Create rope arrow
        EntityRopeArrow arrow = new EntityRopeArrow(
            this.archer.level(),
            this.archer
        );

        // Calculate trajectory
        double dx = this.target.getX() - this.archer.getX();
        double dy = this.target.getY(0.33) - arrow.getY();
        double dz = this.target.getZ() - this.archer.getZ();
        double horizontalDist = Math.sqrt(dx * dx + dz * dz);

        // Shoot with some arc for distance
        float velocity = 1.6F;
        float inaccuracy = 2.0F; // Some spread for difficulty

        arrow.shoot(dx, dy + horizontalDist * 0.2, dz, velocity, inaccuracy);

        // Play sound
        this.archer.level().playSound(
            null,
            this.archer.getX(),
            this.archer.getY(),
            this.archer.getZ(),
            SoundEvents.ARROW_SHOOT,
            this.archer.getSoundSource(),
            1.0F,
            1.0F / (this.archer.getRandom().nextFloat() * 0.4F + 0.8F)
        );

        // Spawn arrow
        this.archer.level().addFreshEntity(arrow);

        TiedUpMod.LOGGER.debug(
            "[KidnapperArcherRangedGoal] {} shot arrow at {}",
            this.archer.getNpcName(),
            this.target.getName().getString()
        );
    }
}
