using Cirno.Scripts;
using Godot;
using System;
using System.Diagnostics;
using Cirno.Scripts.Components;
using Cirno.Scripts.Enums;
using Godot.Collections;

public partial class Enemy : CharacterBody2D
{
	private InteractionController _cachedPlayer;
	public InteractionController CachedPlayer
	{
		get => _cachedPlayer;
		protected set => _cachedPlayer = value;
	}

	private EnemyState _currentState = EnemyState.Idle;

	[Export] public float Health = 4f;

	[Export] public float WalkSpeed = 2500f;

	[Export] public float PlayerDisengageRange = 500f;

	[Export] public float AlarmReactRange = 200f;

	[Export] public Weapon EquippedWeapon;
	[Export] public Node2D DefeatScript;
	
	[Export] public AiState Ai { get; private set; }
	
	[Export] public PackedScene CorpseTemplate { get; private set; }

	protected float _currentHealth = 0f;

	private bool _isDestroyed = false;
	
	private NavigationAgent2D _navigationAgent;
	
	protected bool _invulnerable = false;

	#region Manual Movement

	private Vector2 _movementDirection { get; set; } = Vector2.Zero;
	private Vector2 _facingDirection { get; set; } = Vector2.Right;

	#endregion
	
	private bool IsPlayerInRange => _playerDetection is { IsPlayerInRange: true };

	private bool IsPlayerInSight => _playerDetection is not null && _playerDetection.IsPlayerInSight(CollisionMask);

	private Vector2? _lastPlayerPosition = null;

	[Export]
	private PlayerDetection _playerDetection;

	[Export]
	private bool _navigationEnabled = false;
	
	private AlarmManager _alarmManager;
	public bool NavigationEnabled
	{
		get => Ai is AiState.Enabled && _navigationEnabled && _navigationAgent != null;
		set => _navigationEnabled = value;
	}

	#region Events

	[Signal]
	public delegate void HealthChangedEventHandler(float newValue);

	#endregion
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		_currentHealth = Health;
		
		_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");

		_alarmManager = this.GetAlarmManager();

		if (_alarmManager != null)
		{
			_alarmManager.AlarmEnabled += AlarmManagerOnAlarmEnabled;
		}
	}

	public override void _ExitTree()
	{
		if (_alarmManager == null) return;
		_alarmManager.AlarmEnabled -= AlarmManagerOnAlarmEnabled;
	}

	private void AlarmManagerOnAlarmEnabled(Vector2 location)
	{
		if (NavigationEnabled && location.DistanceTo(this.GlobalPosition) <= AlarmReactRange)
		{
			GD.Print($"Enemy {Name} alerted");
			this._currentState = EnemyState.Alert;
			_lastPlayerPosition = location;
		}
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
		// switch (_currentState)
		// {
		// 	case EnemyState.Idle:
		//
		// 		break;
		// 	case EnemyState.Alert:
		// 		break;
		//
		// 	//case EnemyState.Shooting:
		// 	// Shoot
		// 	//break;
		// 	default:
		// 		break;
		// }

		if (Ai is AiState.Controlled && !_isDestroyed)
		{
			_movementDirection = GetInput();
			_facingDirection = _movementDirection;
		}
	}
	
	private Vector2 GetInput()
	{
		return Input.GetVector("left", "right", "up", "down");
	}

	public override void _PhysicsProcess(double delta)
	{
		if (_isDestroyed) return;
		
		switch (Ai)
		{
			case AiState.Enabled:
				HandleAi(delta);
				return;
			case AiState.Controlled:
				HandleManualControl(delta);
				break;
		}
	}

	private void HandleManualControl(double delta)
	{
		Velocity = _movementDirection * (float)(WalkSpeed * delta);
		MoveAndSlide();
	}

	private void HandleAi(double delta)
	{
		switch (_currentState)
		{
			case EnemyState.Idle:
				if (_playerDetection != null && _playerDetection.IsPlayerInSight(CollisionMask))
				{
					_currentState = EnemyState.Alert;
				}
				//HandlePlayerDetection();

				break;
			case EnemyState.Alert:

				// Update last known player position if it's in range
				if (IsPlayerInRange)
				{
					_lastPlayerPosition = _playerDetection.CachedPlayer.GlobalPosition;
				}
				
				if (NavigationEnabled)
				{
					if (_lastPlayerPosition.HasValue)
					{
						_navigationAgent.SetTargetPosition(_lastPlayerPosition.Value);
					}
					
					var currentAgentPosition = GlobalPosition;
			
					var nextPathPosition = _navigationAgent.GetNextPathPosition();
				
					var newVelocity = currentAgentPosition.DirectionTo(nextPathPosition) * (float)(WalkSpeed * delta);
				
				
					// Navigation is over, can do other things like shooting
					if (_navigationAgent.IsNavigationFinished())
					{
						// Shoot player
						if (IsPlayerInSight)
						{
							Shoot();
						}
					
						// TODO: If player totally left the max range it should stop shooting and go back to idle
					
						return;
					}
				
					if (_navigationAgent.AvoidanceEnabled)
					{
						_navigationAgent.SetVelocity(newVelocity);
			
					}
					else
					{
						_on_navigation_agent_2d_velocity_computed(newVelocity);
					}

					MoveAndSlide();
				}
				else
				{
					if (IsPlayerInSight)
					{
						Shoot();
					}
				}
				
				break;
			case EnemyState.Patrolling:
				break;
			default:
				throw new ArgumentOutOfRangeException();
		}
	}
	
	public void _on_navigation_agent_2d_velocity_computed(Vector2 safeVelocity)
	{
		this.Velocity = safeVelocity; 
		
	}

	protected virtual void Shoot()
	{
		if (EquippedWeapon == null || !_lastPlayerPosition.HasValue) return;
		
		// Shoot at the player's last known position
		EquippedWeapon.ShootDirection = (_lastPlayerPosition.Value - this.GlobalPosition).Normalized();
		
		EquippedWeapon.Shoot();
	}
	
	private void _on_damage_hitbox_area_entered(Area2D area)
	{
		if (area is not Bullet bullet) return;
		if (!bullet.Enabled) return;
		if (_invulnerable) return;
		if (bullet.BulletInfo.Owner == BulletOwner.Enemy) return;
		
		this.Hit(bullet.Damage);
		bullet.RequestCollisionDestruction();
	}

	// Bullets collision
	private void _on_area_entered(Area2D area)
	{
		
	}
	
	protected virtual void Explode()
	{
		Debug.WriteLine("Ded");
		if (DefeatScript is not null)
		{
			ActivateDefeatScript();
		}
		//CreateParticles();
		CreateDebris();
		QueueFree();
	}

	private void CreateDebris()
	{
		if (CorpseTemplate is not null)
		{
			this.CreateSibling<Node2D>(CorpseTemplate);
		}
	}
	
	protected virtual void ActivateDefeatScript()
	{
		if (DefeatScript is not IActivable target)
		{
			GD.PrintErr($"Target {DefeatScript.Name} is not activable");
			return;
		}
        
		target?.Activate();
        
		GD.Print($"{DefeatScript.Name} activated");
	}

	public void Hit(float damage)
	{
		if (_isDestroyed) return;
		if (_invulnerable) return;
		
		_currentHealth -= damage;
		EmitSignal(SignalName.HealthChanged, _currentHealth);
		if (!(_currentHealth <= 0)) return;
		_isDestroyed = true;
		Explode();
	}

	public bool IsDestroyed()
	{
		return _isDestroyed;
	}

	public void AssumeControl()
	{
		GD.Print("Assuming direct control");
		Ai = AiState.Controlled;
	}
}

public enum AiState
{
	Enabled,
	Disabled,
	Controlled
}
