using Cirno.Scripts.Components.Actors;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Utils;
using Godot;

namespace Cirno.Scripts.Components.FSM.Enemy;

public partial class Shooting : EnemyStateBase
{
    public override EnemyState StateId => EnemyState.Shooting;
    
    [Export]
    public EnemyStorageModule StorageModule { get; private set; }
    
    [Export]
    public PlayerDetectionModule PlayerDetection { get; private set; }
    
    [Export]
    public GenericDamageReceiver DamageReceiver { get; private set; }
    
    [Export]
    public NavigationMovementModule NavigationModule { get; private set; }

     // [Export] public float MaxStrafeDistance { get; private set; } = 64f;
     // [Export] public float MinStrafeDistance { get; private set; } = 16f;
    
    [Export] public Weapon EquippedWeapon;
    
    private bool _isPlayerInRange = false;
    
    private Vector2? _currentStrafeTarget = null;

    private float _strafeSpeed => StorageModule.EnemyData.StrafeSpeed;

    private double _responseTimer = 0;
    
    public override void EnterState()
    {
        base.EnterState();
        
        PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange;

        DamageReceiver.ChangeState(true);
        
        DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;

        EquippedWeapon.WeaponData = StorageModule.Root.EnemyResource.Weapon;

        _currentStrafeTarget = null;
        
        _responseTimer = 0;
    }
    
    private void HealthProviderOnResourceDepleted()
    {
        ChangeState(EnemyState.Dead);
    }
    
    private void PlayerDetectionOnPlayerOutOfRange()
    {
        StateMachine.SetState(EnemyState.Alert);
    }
    
    public override void ExitState()
    {
        base.ExitState();
        
        PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
        
        DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
        
        _currentStrafeTarget = null;
        
        DamageReceiver.ChangeState(false);
        
    }

    public override void PhysicsProcessState(double delta)
    {
        base.PhysicsProcessState(delta);
        
        if (PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.ViewRange) && PlayerDetection.IsPlayerInSight())
        {
            // SHOOT
            Shoot();
            
            if (_strafeSpeed > 0)
            {
                // Check if a strafe position is needed
                if (!_currentStrafeTarget.HasValue || NavigationModule.IsNavigationFinished())
                {
                    if (_responseTimer < StorageModule.EnemyData.ResponseTime)
                    {
                        _responseTimer += delta;
                    }
                    else
                    {
                        _currentStrafeTarget = CalculateStrafePosition();
                        _responseTimer = 0;
                    }
                }
            }
        }
        else
        {
            StateMachine.SetState(EnemyState.Alert);
            return;
        }
        
        if (_currentStrafeTarget.HasValue)
        {
            NavigationModule.SetTarget(_currentStrafeTarget.Value);
            NavigationModule.Move(_strafeSpeed);
        }
    }
    
    private Vector2? CalculateStrafePosition()
    {
        Vector2 playerPos = PlayerDetection.LastKnownPlayerPosition.Value;
        Vector2 enemyPos = MainObject.GlobalPosition;
      
        // Calculate direction to player
        Vector2 directionToPlayer = (playerPos - enemyPos).Normalized();
    
        // Get perpendicular vectors (left and right strafing directions)
        Vector2 leftStrafe = directionToPlayer.Rotated(-Mathf.Pi / 2);
        Vector2 rightStrafe = directionToPlayer.Rotated(Mathf.Pi / 2);
    
        // Randomly decide left or right first
        bool tryLeftFirst = GD.Randf() < 0.5f;
    
        for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
        {
            float strafeDistance = Mathf.Lerp(StorageModule.EnemyData.MinStrafeDistance, StorageModule.EnemyData.MaxStrafeDistance, factor);
            Vector2 newPos = enemyPos + (tryLeftFirst ? leftStrafe : rightStrafe) * strafeDistance;
            if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
            {
                return newPos;
            }
        }
    
        for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
        {
            float strafeDistance = Mathf.Lerp(StorageModule.EnemyData.MinStrafeDistance, StorageModule.EnemyData.MaxStrafeDistance, factor);
            Vector2 newPos = enemyPos + (tryLeftFirst ? rightStrafe : leftStrafe) * strafeDistance;
            if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
            {
                return newPos;
            }
        }
    
        return null; // No valid strafe position found
    }

    private Vector2 GetShootDirection()
    {
        if (StorageModule.EnemyData.PredictPlayer && PlayerDetection.LastKnowPlayerVelocity.HasValue)
        {
            var predictedDirection = MathFunctions.PredictInterceptPosition(MainObject.GlobalPosition,
                PlayerDetection.LastKnownPlayerPosition.Value, PlayerDetection.LastKnowPlayerVelocity.Value,
                EquippedWeapon.WeaponData.BulletData.BulletSpeed);
            if (predictedDirection.HasValue) return (predictedDirection.Value - MainObject.GlobalPosition).Normalized();
            
        }
        
        return ( PlayerDetection.LastKnownPlayerPosition.Value - MainObject.GlobalPosition).Normalized();
    }
    
    private void Shoot()
    {
        if (EquippedWeapon == null) return;
        if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return;
        
        var direction = GetShootDirection();
        
        // Shoot at the player's last known position
        EquippedWeapon.ShootDirection = direction;
        //StorageModule.FacingDirection = direction;
        StorageModule.FacingDirection = direction.SnapToCardinal().Normalized();
        StorageModule.AimingDirection = StorageModule.FacingDirection;
        
        EquippedWeapon.Shoot();
    }
}