using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;

public class NPCControllerBase : MonoBehaviour
{
    [Header("Settings:")]
    [SerializeField] protected float projectileRange;
    [SerializeField] protected float meleeRange;
    [SerializeField] protected float distanceToChangePatrolDestination;
    [SerializeField] protected float patrolAgentSpeed;
    [SerializeField] protected float chasingAgentSpeed;
    [SerializeField] protected float timeBetweenAttacks = 1f; //change in editor

    public float ProjectileRange => projectileRange;
    public float MeleeRange => meleeRange;

    [HideInInspector]
    public PhotonView photonView;
    [HideInInspector]
    public Taggable myTag;
    [HideInInspector]
    public NPCAnimatorControllerBase animatorController;
    [HideInInspector]
    public NPCAbilityPriorityManager abilityPriorityManager;
    [HideInInspector]
    public AbilityCooldownTracker abilityCooldownTracker;

    protected NavMeshAgent agent;

    public List<Taggable> possibleTargets = new List<Taggable>();
    public Taggable currentTarget;

    protected BaseAbility ability;

    protected float targetDistance = float.MaxValue;
    protected float distance;
    protected Taggable resultTarget;

    protected Vector3 patrolDestination = new Vector3();

    protected float counter = 0f;

    protected Health health;
    protected Mana mana;

    public Health Health => health;
    public Mana Mana => mana;

    protected bool isDead = false;

    protected bool waitingForAttackAnimation = false;

    public UnityEvent onPossibleTargetEnteredSight = new UnityEvent();
    public UnityEvent onPossibleTargetExitedSight = new UnityEvent();

    protected virtual void Awake()
    {
        myTag = GetComponentInParent<Taggable>();
        agent = GetComponentInParent<NavMeshAgent>();
        photonView = GetComponentInParent<PhotonView>();
        health = GetComponent<Health>();
        mana = GetComponent<Mana>();
        abilityPriorityManager = GetComponentInChildren<NPCAbilityPriorityManager>();
        abilityCooldownTracker = GetComponentInChildren<AbilityCooldownTracker>();

        animatorController = GetComponentInChildren<NPCAnimatorControllerBase>();

        if (!photonView.IsMine) return;

        onPossibleTargetEnteredSight.AddListener(OnNewTargetIdentified);
        onPossibleTargetExitedSight.AddListener(OnPossibleExistingTargetLost);
    }

    protected virtual void Start()
    {
        if (!photonView.IsMine) return;

        isDead = false;

        counter = timeBetweenAttacks / 2f;

        health.onDeath.AddListener(OnDeath);
    }

    protected virtual void Update()
    {
        if (!photonView.IsMine) return;
        if (isDead) return;

        counter += Time.deltaTime;

        if (HasTarget())
        {
            ChasingUpdate();
        }
        else
        {
            PatrollingUpdate();
        }
    }

    #region Checks
    /// <summary>
    /// </summary>
    /// <returns>possibleTargets.Count > 0</returns>
    public virtual bool HasAvailableTargets()
    {
        return possibleTargets.Count > 0;
    }
    /// <summary>
    /// </summary>
    /// <returns>currentTarget != null</returns>
    public virtual bool HasTarget()
    {
        return currentTarget != null;
    }
    /// <summary>
    /// </summary>
    /// <returns>possibleTargets.Contains(currentTarget)</returns>
    public virtual bool HasVisionOfCurrentTarget()
    {
        return possibleTargets.Contains(currentTarget);
    }
    /// <summary>
    /// </summary>
    /// <returns>agent.destination != null</returns>
    public virtual bool HasDestination()
    {
        return agent.destination != null;
    }
    /// <summary>
    /// </summary>
    /// <param name="positionCheck"></param>
    /// <param name="distanceCheck"></param>
    /// <returns> Distance between agent.transform.position and positionCheck less than distanceCheck</returns>
    public virtual bool IsCloseEnough(Vector3 positionCheck, float distanceCheck)
    {
        return Vector3.Distance(agent.transform.position, positionCheck) <= distanceCheck;
    }
    /// <summary>
    /// </summary>
    /// <returns> counter >= timeBetweenAttacks</returns>
    public virtual bool IsReadyToAttack()
    {
        return counter >= timeBetweenAttacks;
    }
    #endregion

    protected virtual void ResetCounterOnAttackPerformed()
    {
        counter = 0;
    }
    protected virtual Taggable GetClosestTarget()
    {
        targetDistance = float.MaxValue;
        resultTarget = null;

        Debug.Log("CLOSEST COUNT: " + possibleTargets.Count);
        for (int i = 0; i < possibleTargets.Count; i++)
        {
            distance = Vector3.Distance(possibleTargets[i].transform.position, agent.transform.position);
            if (distance < targetDistance)
            {
                targetDistance = distance;
                resultTarget = possibleTargets[i];
            }
        }

        return resultTarget;
    }
    protected virtual Taggable GetFurthestAwayTarget()
    {
        targetDistance = 0f;
        resultTarget = null;

        for (int i = 0; i < possibleTargets.Count; i++)
        {
            distance = Vector3.Distance(possibleTargets[i].transform.position, agent.transform.position);
            if (distance > targetDistance)
            {
                targetDistance = distance;
                resultTarget = possibleTargets[i];
            }
        }

        return resultTarget;
    }
    protected virtual void UpdateCurrentTarget(Taggable target)
    {
        if (target == null)
        {
            PatrolNewPosition();
            return;
        }
        currentTarget = target;
        SetupAgentStats(currentTarget.transform.position, true);
    }
    protected virtual void UpdatePatrolTarget(Vector3 destination)
    {
        SetupAgentStats(destination);
    }
    protected virtual void SetupAgentStats(Vector3 destination, bool chasing = false)
    {
        if (isDead) return;

        agent.speed = chasing ? chasingAgentSpeed : patrolAgentSpeed;
        patrolDestination = destination;
        patrolDestination.y = 0f;
        agent.SetDestination(patrolDestination);
    }
    protected virtual void SetAgentMoving(bool isMoving)
    {
        if (isDead) return;

        agent.isStopped = !isMoving;
    }


    protected virtual void OnNewTargetIdentified()
    {
        if (HasTarget())
        {
            OnNewTargetIdentifiedAndHasTarget();
        }
        else //no current target
        {
            OnNewTargetIdentifiedAndNoCurrentTarget();
        }
    }

    protected virtual void OnPossibleExistingTargetLost()
    {
        if (HasTarget())
        {
            if (HasVisionOfCurrentTarget()) //current target inside sight (possibleTargets list)
            {
                OnPossibleTargetLostAndHasTargetAndVision();
            }
            else //current target outside sight (possibleTargets list)
            {
                OnPossibleTargetLostAndHasTargetButNoVision();
            }
        }
        else //no current target
        {
            if (HasAvailableTargets())
            {
                OnPossibleTargetLostAndHasNoCurrentTargetButHasAvailableTargets();
            }
            else //no available targets in sight
            {
                OnPossibleTargetLostAndHasNoCurrentTargetAndNoAvailableTargets();
            }
        }
    }

    protected virtual void OnNewTargetIdentifiedAndHasTarget()
    {
        //someone entered sight, npc already has a target
    }
    protected virtual void OnNewTargetIdentifiedAndNoCurrentTarget()
    {
        //someone entered sight, npc does not have a target yet
    }
    protected virtual void OnPossibleTargetLostAndHasTargetAndVision()
    {
        //someone exited sight, npc already has target and vision
    }
    protected virtual void OnPossibleTargetLostAndHasTargetButNoVision()
    {
        //someone exited sight, npc already has target but no vision of it (possibly his target was the one getting out of sight)
        currentTarget = null;
    }
    protected virtual void OnPossibleTargetLostAndHasNoCurrentTargetButHasAvailableTargets()
    {
        //someone exited sight, npc has no target yet, there are available targets to pick
    }
    protected virtual void OnPossibleTargetLostAndHasNoCurrentTargetAndNoAvailableTargets()
    {
        //someone exited sight, npc has no target yet, there are NO available targets
    }


    protected virtual void TryAttack()
    {

    }


    protected virtual void ChasingUpdate()
    {
        if (IsReadyToAttack())
        {
            TryAttack();
        }
        else
        {
            SetupAgentStats(currentTarget.transform.position, true);

            if (agent.remainingDistance > agent.stoppingDistance)
                SetAgentMoving(true);
            else
                SetAgentMoving(false);
        }
    }

    protected virtual void PatrollingUpdate()
    {
        if (!HasAvailableTargets())
        {
            if (currentTarget != null)
            {
                currentTarget = null;
            }
            if (agent.destination == null)
            {
                PatrolNewPosition();
            }
            else if (agent.remainingDistance < distanceToChangePatrolDestination)
            {
                PatrolNewPosition();
            }
        }
        else
        {
            Debug.Log("Patrolling update, available targets, waiting for sight to do its job");
        }
    }






    public virtual void OnAttackAnimationEventTriggered()
    {
        //execute ability
    }
    public virtual void OnDeathAnimationEventTriggered()
    {
        DestroyAfterEffect();
    }

    protected virtual void OnDeath()
    {
        photonView.RPC(nameof(RPC_OnDeath), RpcTarget.All);
    }

    [PunRPC]
    protected virtual void RPC_OnDeath(bool lootDropped = false)
    {
        if (isDead) return;

        Debug.Log($"{this.gameObject.name} died!");

        isDead = true;

        agent.enabled = false;

        if (!photonView.IsMine) return;

        animatorController.SetDead();
    }

    protected virtual void DestroyAfterEffect()
    {
        if (!photonView.IsMine) return;

        PhotonNetwork.Destroy(this.gameObject);
    }

    protected virtual void PatrolNewPosition()
    {
        agent.speed = patrolAgentSpeed;
        patrolDestination.x = Random.Range(-5, 5);
        patrolDestination.y = 0f;
        patrolDestination.z = Random.Range(-5, 5);
        UpdatePatrolTarget(transform.position + patrolDestination);
        SetAgentMoving(true);
    }

}
