using Godot;
using System;
using System.Diagnostics;
using System.Linq;
using Cirno.Scripts;
using Cirno.Scripts.Components;
using Cirno.Scripts.Components.Actors;
using Cirno.Scripts.Resources;
using Godot.Collections;
using System.Threading.Tasks;
using Cirno.Scripts.Controllers;

public partial class PlayerMovement : CharacterBody2D, IDestructible
{
	[Export]
	public int Speed { get; set; } = 400;

	[Export]
	public int StrafeSpeed { get; set; } = 200;

	public int MovementSpeed => _isStrafing ? StrafeSpeed : Speed;

	[Export]
	public float CrosshairDistance { get; set; } = 10f;

	[Export]
	public PackedScene SelectorScene { get; set; }

	[Export]
	public string GameOverScene { get; set; }

	[Export]
	public Texture2D WingsSprite { get; set; }

	private Selector _selector;

	//private Interactable _lastInteractable;

	[Export]
	public Marker2D Muzzle { get; set; }

	private AnimatedSprite2D _animatedSprite;

	private Vector2 _movementDirection { get; set; }
	private Vector2 _facingDirection { get; set; }

	private Vector2 _rightStickInput { get; set; }

	private Sprite2D _crosshair;

	[Export] public float MaxHealth = 32f;
	[Export] public float MaxShield = 32f;
	[Export] public Shader BlinkShader { get; set; }
	[Export] public Sprite2D HitboxSprite { get; set; }
	[Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.Player;

	[ExportGroup("Action Names")]
	[Export] private string _shootActionName = "shoot";
	[Export] private string _useActionName = "Use";
	[Export] private string _strafeActionName = "strafe";
	[Export] private string _nextWeaponActionName = "next_weapon";
	[Export] private string _previousWeaponActionName = "previous_weapon";
	[ExportCategory("Particles")]
	[Export] private PackedScene _deathParticles;
	[Export] private GpuParticles2D _shieldParticles;

	private PlayerState _state;

	private bool _isDestroyed = false;

	private GameManager _gameManager;

	private InventoryManager _inventoryManager;

	private ActorResourceProvider _healthProvider;
	private ActorResourceProvider _shieldProvider;

	private bool _isStrafing { get; set; }

	private bool _canMove = true;

	public Weapon EquippedWeapon => _weaponProvider.EquippedWeapon;

	[Signal]
	public delegate void HealthChangedEventHandler(float newHealth, float maxHealth);

	[Signal]
	public delegate void ShieldChangedEventHandler(float newShield, float maxShield);

	[Signal]
	public delegate void InteractableAreaEnteredEventHandler(Interactable interactable);

	[Signal]
	public delegate void InteractableAreaExitedEventHandler(Interactable interactable);

	[Signal]
	public delegate void DeathEventHandler();

	public float CurrentHealth
	{
		get => _healthProvider.CurrentResource;
		set => _healthProvider.CurrentResource = value;
	}

	public float CurrentShield
	{
		get => _shieldProvider.CurrentResource;
		set => _shieldProvider.CurrentResource = value;
	}

	private Vector2 _lastCheckPointPosition;

	public Vector2 LastCheckPointPosition
	{
		get => _lastCheckPointPosition;
		set => _lastCheckPointPosition = value;
	}

	private PlayerWeaponProvider _weaponProvider;

	public override void _Ready()
	{
		// CurrentHealth = MaxHealth;
		// CurrentShield = MaxShield;

		_state = PlayerState.Active;

		_weaponProvider = GetNode<PlayerWeaponProvider>("WeaponProvider");
		_weaponProvider.Init(this);

		_animatedSprite = GetNode<AnimatedSprite2D>("./Smoothing2D/AnimatedSprite2D");
		_crosshair = GetNode<Sprite2D>("./Smoothing2D/Crosshair");

		_healthProvider = GetNode<ActorResourceProvider>("HealthProvider");
		_shieldProvider = GetNode<ActorResourceProvider>("ShieldProvider");

		_healthProvider.MaxResource = MaxHealth;
		_shieldProvider.MaxResource = MaxShield;
		_healthProvider.ResourceChanged += (value, maxValue) =>
		{
			EmitSignal(nameof(HealthChanged), value, maxValue);
		};
		_shieldProvider.ResourceChanged += (value, maxValue) =>
		{
			EmitSignal(nameof(ShieldChanged), value, maxValue);
		};
		_healthProvider.FillResource();
		_shieldProvider.FillResource();

		_movementDirection = Vector2.Zero;
		_facingDirection = Vector2.Zero;
		_rightStickInput = Vector2.Zero;
		_isStrafing = false;

		_gameManager = GameManager.Instance;

		//_gameManager = this.GetGameManager();
		_inventoryManager = this.GetInventoryManager();

		_gameManager.GameStateChange += GameManagerOnGameStateChange;

		if (SelectorScene != null)
		{
			_selector = this.CreateSibling<Selector>(SelectorScene, this.GlobalPosition);
			_selector.Visible = false;
		}

		_inventoryManager.ItemUsed += this.UseItem;

		_lastCheckPointPosition = GlobalPosition;

		_ = UnTeleport();
	}

	private void GameManagerOnGameStateChange(GameState state)
	{
		switch (state)
		{
			case GameState.Menu:
			case GameState.Paused:
				_canMove = false;
				break;
			case GameState.Playing:
				_canMove = true;
				_state = PlayerState.Active;
				break;
			case GameState.Dialogue:
				_canMove = false;
				break;
			case GameState.Controlling:
				_canMove = false;
				_state = PlayerState.Controlling;
				break;
		}
	}

	/// <summary>
	/// Requests disable movement
	/// </summary>
	/// <param name="disable">true disables false enables</param>
	public void RequestMovementDisable(bool disable)
	{
		if (disable)
		{
			_canMove = false;
		}
		else
		{
			_canMove = true;
		}
	}

	public void AddWeapon(Weapon weapon)
	{
		_weaponProvider.Equip(weapon, false);
	}

	public void EquipWeapon(string itemKey)
	{
		_weaponProvider.Equip(itemKey, true);
	}

	public void UseItem(LootItem item, int currentAmount)
	{
		GD.Print($"Used item on player {item.ItemKey}");

		switch (item.Item)
		{
			case ItemTypes.FrogBomb:

				_inventoryManager.RemoveItem(item.ItemKey, 1);
				// emit projectile
				var bulletData = item.WeaponData.MakeBullet(this.GlobalPosition);

				var bullet = PoolingManager.Instance.SpawnBullet(bulletData.OriginalBulletResource);

				bullet.GlobalPosition = this.GlobalPosition;
				
				//var bullet = this.CreateChildOf<Bullet>(_gameManager.BulletsContainer, item.WeaponData.BulletData.BulletScene, this.GlobalPosition);
				
				bullet.Initialize(bulletData, _gameManager);

				//bullet.SetDirection(ShootDirection);
				bullet.SetDirection(_facingDirection);
				bullet.Speed = item.WeaponData.BulletData.BulletSpeed;
				RequestMovementDisable(true);
				// set camera
				_gameManager.CameraTargetObject(bullet);
				// set event destroy
				bullet.OnDestroy += () =>
				{
					_gameManager.CameraTargetPlayer();
					RequestMovementDisable(false);
				};
				// go back
				break;

		}
		//var item = _inventoryManager.getitem

	}

	// Triggered by event in inventorymanager
	public void EquipWeapon(Weapon weapon)
	{
		_weaponProvider.Equip(weapon, true);
	}

	private void FindInteractable()
	{
		var selected = _selector.SelectedInteractable;
		if (!Input.IsActionJustPressed(_useActionName) || selected == null) return;
		if (!selected.CanActivate()) return;
		bool success = selected.Activate();

		if (success)
		{
			// Deselect and scan for next
			_selector.RemoveInteractable(selected);
			//_selector.SelectedInteractable = null;
			//_selector.SelectNext();
		}

		//var spaceState = GetWorld2D().DirectSpaceState;

		//var query = PhysicsRayQueryParameters2D.Create(Vector2.Zero, )
	}

	private void HandleShoot()
	{
		if (!Input.IsActionPressed(_shootActionName)) return;
		_weaponProvider.Shoot(this._facingDirection);
	}

	private void SetAnimation()
	{

		if (Velocity.X == 0 && Velocity.Y == 0)
		{
			_animatedSprite.SpeedScale = 0;
		}
		else
		{
			_animatedSprite.SpeedScale = 1;
		}

		if (Velocity.X > 0)
		{
			_animatedSprite.Play("walk_right");
		}
		else if (Velocity.X < 0)
		{
			_animatedSprite.Play("walk_left");
		}
		else if (Velocity.Y > 0)
		{
			_animatedSprite.Play("walk_down");
		}
		else if (Velocity.Y < 0)
		{
			_animatedSprite.Play("walk_up");
		}


	}

	public Vector2 GetInput()
	{
		return Input.GetVector("left", "right", "up", "down");
	}

	private Vector2 GetRightStickInput()
	{
		return new Vector2(
			Input.GetAxis("aim_left", "aim_right"),
			Input.GetAxis("aim_up", "aim_down")
		);
	}

	private Vector2 CalculateCrosshairPosition()
	{

		return _facingDirection * CrosshairDistance;// + this.Position;

		//var angle = Mathf.Atan2(this.Position.X, this.Position.Y);


		//var cPos = new Vector2(this.Position.X + CrosshairDistance * Godot.Mathf.Cos(angle), this.Position.Y + CrosshairDistance * Godot.Mathf.Sin(angle));
	}

	public override void _Process(double delta)
	{
		if (_isDestroyed)
		{
			if (Input.IsActionJustPressed(_shootActionName))
				Respawn();
			return;
		}
		;

		SetAnimation();
		if (!_canMove) return;

		_movementDirection = GetInput();

		_isStrafing = Input.IsActionPressed(_strafeActionName);
		// Toggle visibility of the hitbox sprite based on strafing
		if (HitboxSprite != null)
		{
			HitboxSprite.Visible = _isStrafing;
		}

		_rightStickInput = GetRightStickInput();

		// Update Facing Direction
		if (!_isStrafing)
		{
			if (_rightStickInput.Length() > 0.1f) // If the right stick is moved
			{
				_facingDirection = _rightStickInput.Normalized();
			}
			else if (_movementDirection != Vector2.Zero) // Fall back to movement direction
			{
				_facingDirection = _movementDirection;
			}
		}

		HandleShoot();
		FindInteractable();

		if (Input.IsActionJustPressed(_nextWeaponActionName))
		{
			_weaponProvider.NextWeapon();
		}

		if (Input.IsActionJustPressed(_previousWeaponActionName))
		{
			_weaponProvider.PreviousWeapon();
		}

		_crosshair.Position = CalculateCrosshairPosition();
	}

	public void Respawn()
	{
		if (!_isDestroyed) return;
		_isDestroyed = false;
		this.GlobalPosition = LastCheckPointPosition;
		_healthProvider.FillResource();
		this.Visible = true;
	}

	public override void _PhysicsProcess(double delta)
	{
		if (_isDestroyed) return;
		if (!_canMove) return;

		Velocity = _movementDirection * MovementSpeed;

		MoveAndSlide();
	}

	private void _on_interaction_controller_area_entered(Area2D area)
	{
		if (!_canMove) return;
		// Replace with function body.
		if (area.IsInGroup("Interactable") && area is Interactable interactable && interactable.CanActivate())
		{
			Debug.WriteLine($"Interactable {area.Name} Entered");

			EmitSignal(nameof(InteractableAreaEntered), interactable);

			if (_selector == null) return;

			_selector.AddInteractable(interactable);

			//_selector.SelectedInteractable = interactable;

		}
	}

	private void _on_interaction_controller_area_exited(Area2D area)
	{
		if (!_canMove) return;
		if (area.IsInGroup("Interactable") && area is Interactable interactable)
		{
			Debug.WriteLine($"Interactable {area.Name} Exited");

			EmitSignal(nameof(InteractableAreaExited), interactable);

			if (_selector == null) return;
			_selector.RemoveInteractable(interactable);
		}
	}

	private void Explode()
	{
		Debug.WriteLine("Ded");
		//CreateParticles();
		//CreateDebris();
		// if (GameOverScene != null)
		// {
		// 	GetTree().ChangeSceneToFile(GameOverScene);
		// }
		// else
		// {
		// 	QueueFree();
		// }
		var particles = this.CreateSibling<AutodeleteParticle>(_deathParticles, this.GlobalPosition);
		//particles.Init();
		this.Visible = false;
		EmitSignal(SignalName.Death);
	}

	public void Hit(float damage, DamageType type = DamageType.Neutral)
	{
		if (!_canMove) return;
		GD.Print($"Player damaged for {damage}");
		if (_isDestroyed) return;

		if (CurrentShield > 0 && type is not DamageType.Explosive or DamageType.Acid)
		{
			// Reduce shield
			PlayShieldAnimation();
			CurrentShield -= damage;
			if (CurrentShield < 0)
			{
				CurrentHealth -= Math.Abs(CurrentShield);
				CurrentShield = 0;
			}
		}
		else
		{
			if (type is DamageType.Fire)
			{
				CurrentHealth -= damage * 2;
			}
			else
			{
				CurrentHealth -= damage;
			}

			Blink();
		}

		if (!(CurrentHealth <= 0)) return;
		_isDestroyed = true;
		Explode();
	}

	private void PlayShieldAnimation()
	{
		if (_shieldParticles is null) return;
		_shieldParticles.Emitting = true;
	}

	public void Blink()
	{
		if (BlinkShader != null)
		{
			_ = BlinkAsync();
		}
	}

	private async Task BlinkAsync()
	{
		((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;

		Tween tween = GetTree().CreateTween();
		tween.TweenMethod(Callable.From((float value) => SetShaderBlinkIntensity(value)), 1f, 0, 0.5);
		await ToSignal(tween, "finished");
	}

	public async Task Teleport()
	{
		((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;

		Tween tween = GetTree().CreateTween();
		tween.TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 0f, 50f, 0.5);
		tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 0f, 1f, 0.5);
		await ToSignal(tween, "finished");
	}

	public async Task UnTeleport()
	{
		((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;

		Tween tween = GetTree().CreateTween();
		tween.TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 1f, 0f, 0.5);
		tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 50f, 0f, 0.5);
		await ToSignal(tween, "finished");
	}

	private void SetShaderTeleportProgress(float value)
	{
		((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("teleport_progress", value);
	}

	private void SetShaderScanlineDensity(float value)
	{
		((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("scanline_density", value);
	}

	private void SetShaderBlinkIntensity(float newValue)
	{
		((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("blink_intensity", newValue);
	}


	public bool IsDestroyed()
	{
		return _isDestroyed;
	}

	private void _on_damage_hit_box_area_entered(Area2D area)
	{
		if (!_canMove) return;
		if (area is Bullet bullet && bullet.BulletOwner != BulletGroup)
		{
			if (!bullet.Enabled) return;
			this.Hit(bullet.Damage, bullet.DamageType);
			bullet.RequestCollisionDestruction();
		}
	}

}

public enum PlayerState
{
	Init,
	Active,
	Cutscene,
	Teleporting,
	UnTeleporting,
	Controlling,
	Dead,
	Drowning,
}