using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Cirno.Scripts;
using Cirno.Scripts.Components;
using Cirno.Scripts.Controllers;
using Cirno.Scripts.Resources;

public partial class Bullet : Area2D
{
    [Export] public float Speed = 1900f;

    public BulletOwner BulletOwner => _bulletInfo?.Owner ?? BulletOwner.None;

    public float Damage => _bulletInfo?.Damage ?? 1;

    public DamageType DamageType => _bulletInfo?.DamageType ?? DamageType.Neutral;

    protected Vector2 _direction = Vector2.Right;

    private double _elapsedTime = 0f;
    private BulletInfo _bulletInfo;

    public BulletInfo BulletInfo => _bulletInfo;

    private List<ModifierWrapper> _modifiers = new();

    private GameManager _gameManager;

    public bool IsGrazed { get; set; } = false;

    public bool IsFrozen { get; private set; } = false;
    public bool Enabled { get; private set; } = false;

    [Signal]
    public delegate void OnDestroyEventHandler();

    private AudioStreamPlayer2D _grazeSound;
    private GpuParticles2D _grazeParticles;

    private CollisionShape2D _collisionShape2D;

    public override void _Ready()
    {
        _grazeSound = GetNodeOrNull<AudioStreamPlayer2D>("AudioStreamPlayer2D");
        _grazeParticles = GetNodeOrNull<GpuParticles2D>("GrazeParticles");

        _collisionShape2D = GetNode<CollisionShape2D>("CollisionShape2D");
    }

    public void Initialize(BulletInfo bulletInfo, GameManager gameManager)
    {
        _bulletInfo = bulletInfo;

        _gameManager = gameManager;

        _elapsedTime = 0f;

        this.Speed = bulletInfo.Speed;

        // Need to clone them here
        // _modifiers = _bulletInfo.TimeModifiers.Select(x => x.MakeClone()).ToList();


        // var clonedModifiers = _bulletInfo.TimeModifiers.Select(x => x.MakeClone());
        // _modifiers = clonedModifiers.ToList();

        // Ugly hack to make instances unique
        _modifiers = _bulletInfo.TimeModifiers.Select(x => x.Wrap()).ToList();
    }

    /// <summary>
    /// Enables the bullet, shows the sprite and activates collisions
    /// </summary>
    public void Enable()
    {
        Enabled = true;
        Show();
        if (this._collisionShape2D is null)
        {
            _collisionShape2D = GetNode<CollisionShape2D>("CollisionShape2D");
        }

        _collisionShape2D.SetDeferred(CollisionShape2D.PropertyName.Disabled, false);
    }

    /// <summary>
    /// Disables the bullet, hides the sprite and disables collisions
    /// </summary>
    public void Disable(bool hideSprite = true)
    {
        Enabled = false;
        if (hideSprite && !BulletInfo.Attributes.HasFlag(BulletFlags.PersistSprite))
        {
            Hide();
        }

        if (this._collisionShape2D is null)
        {
            _collisionShape2D = GetNode<CollisionShape2D>("CollisionShape2D");
        }

        _collisionShape2D.SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
    }

    public void Graze()
    {
        if (!Enabled) return;
        _grazeSound?.Play();
        if (_grazeParticles is not null)
        {
            _grazeParticles.Emitting = true;
        }

        IsGrazed = true;
    }

    private void ApplyTimeModifiers(double delta)
    {
        foreach (var modifier in _modifiers)
        {
            if (_elapsedTime >= modifier.TimeModifier.TimeInSeconds)
            {
                if (!modifier.Applied)
                {
                    modifier.Applied = true;
                    modifier.TimeModifier.Start(this);
                    modifier.Elapsed = 0;
                }
                else
                {
                    modifier.Elapsed += delta;
                }

                modifier.TimeModifier.Update(this, delta, modifier.Elapsed);

                // switch (modifier.ModifierType)
                // {
                // 	case TimeModifierType.SpeedChange:
                // 		//_bulletInfo.Speed += modifier.Value;
                // 		Speed = modifier.Value;
                // 		break;
                // 	case TimeModifierType.RotationChange:
                // 		RotateBullet(modifier.Value);
                // 		//Rotation += Mathf.DegToRad(modifier.Value);
                // 		break;
                // 	case TimeModifierType.FacePlayer:
                // 		FacePlayer();
                // 		break;
                // }

                // if (!modifier.Continuous)
                // {
                // 	modifier.Applied = true;
                // }
            }
        }
    }

    public virtual void RotateBullet(float degrees)
    {
        //SetRotationDegrees(RotationDegrees + degrees);
        float radians = Mathf.DegToRad(degrees);
        _direction = _direction.Rotated(radians).Normalized(); // Rotate direction
        
        if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
        SetRotation(Rotation + radians);
    }

    public virtual void RotateSpriteDegrees(float degrees)
    {
        if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
        SetRotationDegrees(RotationDegrees + degrees);
    }

    public virtual void RotateSprite(float radians)
    {
        if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
        SetRotation(Rotation + radians);
    }

    public void FacePlayer()
    {
        if (_gameManager.Player != null)
        {
            _direction = (_gameManager.PlayerPosition.Value - this.GlobalPosition).Normalized();
            RotateBullet(0); // quick hack to rotate lasers
            //LookAt(player.GlobalPosition);
        }
    }


    //private void OnBodyEntered(Node body)
    //{
    // When a body is entered, invoke the event and pass the collided body
    //	BulletHit?.Invoke(body);

    // Then remove the bullet
    //	QueueFree();
    //}

    public void SetDirection(Vector2 direction)
    {
        var normalized = direction.Normalized();

        _direction = normalized;

        if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
        SetRotation(Mathf.Atan2(normalized.Y, normalized.X) + Mathf.Pi / 2);

        //Debug.WriteLine($"Bullet Shot at direction {direction.X} {direction.Y}");
    }

    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
        if (!Enabled) return;
        _elapsedTime += delta;

        if (_elapsedTime >= _bulletInfo.LifeTime)
        {
            Destroy();
        }
    }

    public override void _PhysicsProcess(double delta)
    {
        if (!Enabled) return;
        if (_bulletInfo != null)
        {
            ApplyTimeModifiers(delta);
        }

        if (BulletInfo.Attributes.HasFlag(BulletFlags.Controllable))
        {
            ControlBullet(delta);
        }

        this.Position += ((float)(Speed * delta) * _direction);
    }

    private void ControlBullet(double delta)
    {
        if (!Enabled) return;
        var axis = Input.GetAxis("left", "right");

        if (axis != 0)
        {
            float rotationSpeed = 180f; // Degrees per second
            RotateBullet(axis * rotationSpeed * (float)delta);
        }
    }

    private void _on_visible_on_screen_notifier_2d_screen_exited()
    {
        if (!Enabled) return;
        if (!BulletInfo.Attributes.HasFlag(BulletFlags.DieOutOfScreen)) return;
        //Debug.WriteLine("Destroy bullet out of screen");
        Destroy();
    }

    private void _on_body_entered(Node2D body)
    {
        if (body.IsInGroup("Solid"))
        {
            //Debug.WriteLine("Collision");
            RequestCollisionDestruction();
        }
        //// Do not Collide with body for purpose of destroying bullets
        // else if (body.IsInGroup("Destroyable"))
        // {
        // 	Debug.WriteLine("Collision with destroyable object body");
        // 	QueueFree();
        // }
    }

    private void _on_area_entered(Area2D area)
    {
        if (area.IsInGroup("Solid"))
        {
            RequestCollisionDestruction();
            return;
        }

        if (area.IsInGroup("Destroyable") && area is IDestructible destructible &&
            CanHit(BulletOwner, destructible.BulletGroup))
        {
            // hit
            destructible.Hit(Damage, DamageType);

            RequestCollisionDestruction();
        }
    }

    public bool CanHit(BulletOwner bulletOwner, BulletOwner targetGroup)
    {
        // If either is None, it always hits
        if (bulletOwner == BulletOwner.None || targetGroup == BulletOwner.None)
        {
            return true;
        }

        // Otherwise, it hits only if they are different groups
        return bulletOwner != targetGroup;
    }

    public void RequestCollisionDestruction()
    {
        if (!Enabled) return;
        if (_bulletInfo.DestroyOnCollision)
        {
            Destroy();
        }
    }

    private void Destroy()
    {
        if (_bulletInfo?.DestructionParticlesScene != null)
        {
            this.CreateSibling<Node2D>(_bulletInfo.DestructionParticlesScene);

            //particle.Init();
        }

        EmitSignal(SignalName.OnDestroy);
        //QueueFree();
        PoolingManager.Instance.DisableBullet(this);
    }

    public void Freeze()
    {
        IsFrozen = true;
        EmitSignal(SignalName.OnDestroy);
        //QueueFree();
        PoolingManager.Instance.DisableBullet(this);
    }
}

public enum BulletOwner
{
    None,
    Player,
    Enemy
}

public enum DamageType
{
    Neutral,
    Ballistic,
    Fire,
    Ice,
    Explosive,
    Acid
}