using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class Unit : MonoBehaviour, IDamageable
{
    private const int ACTION_POINTS_BASE = 2;

    public static event EventHandler OnAnyActionPointsChanged;
    public static event EventHandler OnAnyUnitSpawned;
    public static event EventHandler OnAnyUnitDead;

    [SerializeField] private bool isEnemy;
    [SerializeField] private TurnSystem turnSystem;
    private GridPosition gridPosition;
    MoveAction moveAction;
    private BaseAction[] baseActionArray;
    private SpendablePoints<ActionPoints> actionPoints = ACTION_POINTS_BASE;
    private HealthSystem healthSystem;
    private Dictionary<int, List<IUnitBuff>> buffDictionary = new Dictionary<int, List<IUnitBuff>>();

    private void Awake() {
        healthSystem = GetComponent<HealthSystem>();
        moveAction = GetComponent<MoveAction>();

        baseActionArray = GetComponents<BaseAction>();
    }
    private void Start() {
        gridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
        LevelGrid.Instance.AddUnitAtGridPosition(gridPosition,this);
        turnSystem.OnTurnChanged += TurnSystem_OnTurnChanged;
        healthSystem.OnDead += HealthSystem_OnDead;
        OnAnyUnitSpawned?.Invoke(this, EventArgs.Empty);
    }
    private void Update()
    {
        updateGridPosition();
    }

    public T GetAction<T>() where T: BaseAction =>
        (T)baseActionArray.First(baseAction => baseAction is T);
    public MoveAction GetDefaultAction() =>
        GetAction<MoveAction>();
    

    private void updateGridPosition() {
        GridPosition newGridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
        if (gridPosition != newGridPosition) {
            GridPosition oldGridPosition = gridPosition;
            gridPosition = newGridPosition;
            LevelGrid.Instance.UnitMovedGridPosition(oldGridPosition, newGridPosition, this);
        }
    }

    public GridPosition GetGridPosition () => gridPosition;

    public Vector3 GetWorldPosition () => transform.position;
    
    public BaseAction[] GetBaseActionArray() {
        return baseActionArray;
    }

    public bool TrySpendActionPointToTakeAction(BaseAction baseAction) {
        if (CanSpendActionPointToTakeAction(baseAction)) {
            SpendActionPoints(baseAction.GetActionPointsCost());
            return true;
        } else {
            return false;
        }
    }
    public bool CanSpendActionPointToTakeAction(BaseAction baseAction) => (actionPoints >= baseAction.GetActionPointsCost());
    private void SpendActionPoints(SpendablePoints<ActionPoints> amount) {
        actionPoints -= amount;
        OnAnyActionPointsChanged?.Invoke(this, EventArgs.Empty);
    }

    public void AddActionPoints(SpendablePoints<ActionPoints> amount) {
        actionPoints += amount;
        OnAnyActionPointsChanged?.Invoke(this, EventArgs.Empty);        
    }
    private bool IsMyTurn() => (turnSystem.IsPlayerTurn() != IsEnemy());
    public void TurnSystem_OnTurnChanged(object sender, EventArgs empty) {
        ExpireBuffs();
        if (!IsMyTurn()) {
            return;
        }

        actionPoints = ACTION_POINTS_BASE;
        OnAnyActionPointsChanged?.Invoke(this, EventArgs.Empty);
        ApplyBuffs();
    }
    public void AddBuff (int duration, IUnitBuff buff) {
        int turnExpiration = turnSystem.GetTurnNumber() + duration;
        buff.ApplyBuff(this);
        if (buffDictionary.TryGetValue(turnExpiration, out List<IUnitBuff> buffList)) {
            buffList.Add(buff);
        } else {
            buffList = new List<IUnitBuff>() {
                buff
            };
            buffDictionary.Add(turnExpiration, buffList);            
        }

    }
    private void ApplyBuffs() {
        foreach (var buff in buffDictionary.SelectMany(bufflist => bufflist.Value))
        {
            buff.ApplyBuff(this);
        }


    }
    private void ExpireBuffs() {
        buffDictionary.Remove(turnSystem.GetTurnNumber());
    }
    public SpendablePoints<ActionPoints> GetActionPoints() {
        return actionPoints;
    }

    public bool IsEnemy() => isEnemy;

    public void Damage(SpendablePoints<HealthPoints> damageAmount) {
        healthSystem.Damage(damageAmount);
    }

    private void HealthSystem_OnDead(object sender, EventArgs empty) {
        LevelGrid.Instance.ClearUnitAtGridPosition(gridPosition, this);
        Destroy(gameObject);
        OnAnyUnitDead?.Invoke(this, EventArgs.Empty);
    }

    public float GetHealthNormilized() => healthSystem.GetHealthNormalized();
}