using System;
using System.Linq;
using Cirno.Scripts.Components.FSM.Player;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Utils;
using Godot;
using Godot.Collections;

namespace Cirno.Scripts.Components.Actors;

public partial class PlayerWeaponProvider : Node2D
{
    [Export] public PlayerStorageModule StorageModule { get; set; }
    [Export] public PackedScene WeaponTemplate { get; private set; }

    [Export] public double WeaponSwitchCooldown { get; private set; } = 0.5d;
    
    [Export] public Marker2D WeaponRightOffset { get; private set; }  // local offset when facing right
    [Export] public Marker2D WeaponLeftOffset { get; private set; }  // local offset when facing left
    
    public Array<Weapon> EquippedWeapons { get; set; } = [];

    private int _currentWeaponIndex = 0;

    private double _switchCooldown = 0d;
    private bool _switching = false;
    
    private int CurrentWeaponIndex
    {
        get => Math.Clamp(_currentWeaponIndex, 0, EquippedWeapons.Count -1);

        set
        {
            if (value > EquippedWeapons.Count - 1)
            {
                _currentWeaponIndex = 0;
                return;
            }

            if (value < 0)
            {
                _currentWeaponIndex = EquippedWeapons.Count - 1;
                return;
            }

            _currentWeaponIndex = value;
        }
    }

    private InventoryManager _inventoryManager;

    public Weapon EquippedWeapon { get; set; }

    private CharacterBody2D _parent;

    public void Init(CharacterBody2D parent)
    {
        _parent = parent;

        _inventoryManager = this.GetInventoryManager();

        _inventoryManager.WeaponEquip += this.OnInventoryWeaponEquipped;

        _inventoryManager.ItemAdded += OnInventoryWeaponAdded;
        
        EquipStartupWeapon();
    }

    public void Update(double delta)
    {
        RotateWeapon();
        
        if (!_switching) return;
        _switchCooldown += delta;
        if (_switchCooldown >= WeaponSwitchCooldown)
        {
            _switching = false;
            _switchCooldown = 0d;
        }
    }

    private void RotateWeapon()
    {
        if (EquippedWeapon is null) return;
        
        EquippedWeapon.RotateWeapon(StorageModule.FacingDirection, WeaponLeftOffset.Position, WeaponRightOffset.Position);
        
        
        
        // EquippedWeapon.SetRotation(angle + Mathf.Pi / 2.0f);
        //
        //
        //
        // EquippedWeapon.FlipH = facingLeft;
        //
        // // 3. Position on correct side (assuming EquippedWeapon is a child of the Player node)
        // EquippedWeapon.Position = facingLeft ? WeaponLeftOffset : WeaponRightOffset;
    }
    
    private void OnInventoryWeaponEquipped(string itemKey)
    {
        Equip(itemKey, true);
    }

    private void OnInventoryWeaponAdded(LootItem item, int amount)
    {
        if (item.Item is not ItemTypes.Weapon) return;
        Equip(item, false);
    }

    private void EquipStartupWeapon()
    {
        if (EquippedWeapon is not null) return;
        if (!string.IsNullOrWhiteSpace(GlobalState.Session.EquippedWeaponId))
        {
            // equip it
            Equip(GlobalState.Session.EquippedWeaponId, false);
        }
        else
        {
            // Try to equip whatever is first
            var weaponData = InventoryManager.Instance.Items.FirstOrDefault(x => x.Item.Item is ItemTypes.Weapon);
            if (weaponData is null) return;
            
            Equip(weaponData.Item.ItemKey, false);
            
        }
    }
    
    // This is a soft equip
    public void AddWeapon(Weapon weapon)
    {
        EquippedWeapons.Add(weapon);
    }

    // Triggered by event in inventorymanager
    private void EquipWeapon(Weapon weapon)
    {
        if (EquippedWeapon == weapon)
        {
            return;
        }
        
        // Need to start cooldown
        EquippedWeapon?.Hide();
        
        EquippedWeapon = weapon;
        CurrentWeaponIndex = EquippedWeapons.IndexOf(weapon);
        GlobalState.Session.EquippedWeaponId = weapon.WeaponData.ItemKey;
        
        EquippedWeapon.Show();

        _switching = true;
        _switchCooldown = 0d;
        
        InventoryManager.Instance.UpdateEquippedWeapon(weapon.WeaponData.ItemKey);
    }

    public void NextWeapon()
    {
        CurrentWeaponIndex += 1;

        Equip(EquippedWeapons[CurrentWeaponIndex], true);
    }

    public void PreviousWeapon()
    {
        CurrentWeaponIndex -= 1;

        Equip(EquippedWeapons[CurrentWeaponIndex], true);
    }

    public void Shoot(Vector2 direction)
    {
        if (EquippedWeapon == null) return;
        if (_switching) return;
        
        EquippedWeapon.ShootDirection = direction;
        EquippedWeapon.Shoot();
    }

    public void Reload()
    {
        if (EquippedWeapon == null) return;
        if (_switching) return;
        
        EquippedWeapon.Reload();
    }

    // Remastered method
    private LootItem GetItemFromInventory(string itemKey)
    {
        return InventoryManager.Instance.Items.FirstOrDefault(x => x.Item.ItemKey == itemKey)?.Item;
    }

    private Weapon GetWeaponFromLocal(string itemKey)
    {
        return EquippedWeapons.FirstOrDefault(x => x.WeaponData.ItemKey == itemKey);
    }
    
    // Remastered method
    private Weapon SpawnWeapon(string itemKey)
    {
        return SpawnWeapon(GetItemFromInventory(itemKey));
    }
    
    // Remastered method
    private Weapon SpawnWeapon(LootItem startingItem)
    {
        if (startingItem is null)
        {
            GD.Print($"Could not spawn weapon was not in the inventory.");
            return null;
        }
        
        if (WeaponTemplate == null)
        {
            GD.Print("Could not spawn weapon because template is null");
            return null;
        }
        
        // Check if it's not spawned already
        var maybeExistingWeapon = GetWeaponFromLocal(startingItem.ItemKey);
        if (maybeExistingWeapon is not null) return maybeExistingWeapon;
        
        var weapon = this.CreateSibling<Weapon>(WeaponTemplate);
        weapon.WeaponData = startingItem.WeaponData;

        weapon.Sprite.Texture = startingItem.InventorySprite;
        
        this.AddWeapon(weapon);
        return weapon;
    }

    public Weapon Equip(LootItem item, bool force)
    {
        var maybeExistingWeapon = GetWeaponFromLocal(item.ItemKey);
        if (maybeExistingWeapon is not null) return Equip(maybeExistingWeapon, force);
        
        // Spawn if not present
        var spawnedWeapon = SpawnWeapon(item);
        return Equip(spawnedWeapon, force);
    }
    
    public Weapon Equip(string itemKey, bool force)
    {
        // Check in local inventory first
        var maybeExistingWeapon = GetWeaponFromLocal(itemKey);
        if (maybeExistingWeapon is not null) return Equip(maybeExistingWeapon, force);
        
        // Spawn if not present
        var spawnedWeapon = SpawnWeapon(itemKey);
        if (spawnedWeapon is null)
        {
            GD.Print($"Tried to spawn weapon {itemKey} but failed because it was null.");
            return null;
        };
        return Equip(spawnedWeapon, force);
    }
    
    public Weapon Equip(Weapon weapon, bool force)
    {
        // When we get here we already have a spawned weapon and it's in the list

        if (weapon is null)
        {
            return null;
        }
        
        // Always equip it if there's nothing equipped
        if (this.EquippedWeapon is null)
        {
            this.EquipWeapon(weapon);
            return weapon;
        }

        // If it's a soft equip check for priority
        if (!force && this.EquippedWeapon.WeaponData.Priority < weapon.WeaponData.Priority)
        {
            this.EquipWeapon(weapon);
            return weapon;
        }

        EquipWeapon(weapon);
        return weapon;
    }
}
