using UnityEngine;
using UnityEngine.AI;

public class SkinlessMonsterComponent : MonoBehaviour
{
    [SerializeField] private NavMeshAgent agent;

    [SerializeField] private SkinlessMonsterAnimator animator;

    [SerializeField] private float atTargetDistance = 2;

    [SerializeField] private float health = 2f;

    [SerializeField] [Tooltip("This is the angle of visibility the enemy has")]
    private float visibilityConeLimit = 90f;

    [SerializeField] private float bulletSoundRange = 15f;

    [SerializeField] private float newTargetCooldown = 5f;
    //private bool prePauseStoppedState = false;

    [SerializeField] private BoxCollider leftHandDamage;

    [SerializeField] private BoxCollider rightHandDamage;

    private bool atTarget;
    private float distanceToPlayer;
    private FlareRegister flareRegister;
    private bool inDamageMargin;
    private bool inDamageRange;


    private bool isAlive = true;

    private InGameManager manager;


    private Vector3 oppositeVector;

    private PlayerComponent player;
    private TargetInformation target;

    private GameObject targetObject;
    private float timeSinceTarget;

    private void Awake()
    {
        //Find active player rn.
        var players = FindObjectsOfType<PlayerComponent>();
        foreach (var p in players)
            if (p.isActiveAndEnabled)
                player = p;

        manager = FindObjectOfType<InGameManager>();
    }

    private void Start()
    {
        flareRegister = FindObjectOfType<FlareRegister>();
        if (targetObject == null)
            targetObject = new GameObject();
        targetObject.name = "Enemy Target";

        if (player == null) player = FindObjectOfType<PlayerComponent>();
    }

    private void Update()
    {
        if (!player.IsAlive) Stop();

        if (target != null) HandleTargetOperations();
        CheckForAOERanges();
        if (isAlive && player.IsAlive)
            SetLiveTargeting();
        timeSinceTarget += Time.deltaTime;
        if (isAlive)
        {
            leftHandDamage.enabled = true;
            rightHandDamage.enabled = true;
        }
        else
        {
            leftHandDamage.enabled = false;
            rightHandDamage.enabled = false;
        }


        /*
            AI Behavior:
            A. If not targeting player
                1. Get distance to player
                    - if distance < player.NoiseDistance then target player.
                2. Raycast to player to test line of sight
                    - if in line of sight and angle between LOS vector and forward vector is < LOF margin, then
                    target player.
            B. If targeting player
                1. If out of sound range and no line of sight then lose target.
                    - stop()
                2. If beacon placed down and in range
                    - remove agent
                    - kill()
                3. If beacon placed down and in margin:
                    - stop() and wait for player to leave AOE (enforced by navigation).
         */
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.GetComponent<BulletComponent>() != null)
            health -= other.gameObject.GetComponent<BulletComponent>().DamageMagnitude;
    }

    private void HandleTargetOperations()
    {
        //Update booleans for movement
        var distToTarget = Vector3.Distance(target.target.transform.position, agent.transform.position);
        atTarget = atTargetDistance >= distToTarget;
        if (target.hasDamageRange)
        {
            inDamageRange = target.damageRange <= distToTarget;
            inDamageMargin = target.damageMargin + target.damageRange <= distToTarget;
        }
        else
        {
            inDamageRange = false;
            inDamageMargin = false;
        }

        //Perform actions.
        if (target.runTowards)
        {
            if (inDamageRange)
            {
                //Damage is being dealt
                animator.InLight();
                //Deal Damage
                health -= Time.deltaTime;
                if (health < 0 && isAlive) Kill();
            }
            else if (inDamageMargin)
            {
                //Effective stop
                agent.SetDestination(transform.position);
                animator.StopMoving();
            }
            else
            {
                if (atTarget)
                {
                    //Effective stop
                    agent.SetDestination(transform.position);
                    animator.StopMoving();
                    if (target.isHostile)
                    {
                        animator.Attack();
                        animator.SetAttackType(Random.Range(0, 0));
                    }
                }
                else
                {
                    agent.SetDestination(target.target.transform.position);
                    animator.StartMoving();
                    animator.NotInLight();
                }
            }
        }
        else
        {
            //Run away logic
            if (atTarget || distToTarget < 3)
            {
                animator.StopMoving();
            }
            else
            {
                var r = new Ray();
                r.origin = transform.position;
                r.direction = oppositeVector;
                RaycastHit hit;
                if (Physics.Raycast(r, out hit))
                {
                    animator.StartMoving();
                    agent.SetDestination(hit.point);
                }
                else
                {
                    agent.SetDestination(transform.position + oppositeVector * 100);
                    agent.isStopped = false;
                }
            }
        }

        if (atTarget && target.isHostile) SlowLookAt(target.target.transform.position - transform.position);
    }

    private void SlowLookAt(Vector3 targetVector, float animatedRotationSpeed = 1f)
    {
        var relativePos = targetVector;
        var toRotation = Quaternion.LookRotation(relativePos);

        transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, animatedRotationSpeed * Time.deltaTime);
    }

    private void CheckForAOERanges()
    {
        foreach (var bullet in flareRegister.bullets)
        {
            var dist = Vector3.Distance(bullet.transform.position, transform.position);
            if (dist <= bullet.DamageRange)
            {
                health = 0;
                Kill(true);
            }
        }

        foreach (var beacon in flareRegister.beacons)
        {
            var dist = Vector3.Distance(beacon.transform.position, transform.position);
            if (dist <= beacon.Range) health = 0f;
        }

        if (health <= 0) Kill();
    }

    public bool isPlayerVisible(bool withAngle)
    {
        var r = new Ray();
        r.origin = transform.position;
        r.direction = player.transform.position - transform.position;
        var toPosition = (player.transform.position - transform.position).normalized;
        var angleToPosition = Vector3.Angle(transform.forward, toPosition);


        RaycastHit hit;
        if (Physics.Raycast(r, out hit))
        {
            var hitObject = hit.transform.gameObject;

            if (hitObject.GetComponent<PlayerComponent>() != null)
                //hit player
                return angleToPosition <= visibilityConeLimit || !withAngle;
            if (hitObject.GetComponentInParent<PlayerComponent>() != null)
                //also hit player
                return angleToPosition <= visibilityConeLimit || !withAngle;
        }

        return false;
    }

    private void SetLiveTargeting()
    {
        if (!isAlive)
            //this.targetObject.transform.position = transform.position;
            Stop();

        distanceToPlayer = Vector3.Distance(transform.position, player.transform.position);
        //print("Dist Comparison "+distanceToPlayer.ToString()+" Noise:"+player.NoiseManager.NoiseDistance);
        var isPlayerVisible = this.isPlayerVisible(true);
        //check if player is heard or player line of sight
        if (distanceToPlayer <= player.NoiseManager.NoiseDistance || isPlayerVisible)
        {
            //check that nothing in between
            if (this.isPlayerVisible(false)) Target(player.gameObject, true);
        }
        else
        {
            if (timeSinceTarget < newTargetCooldown)
                //no further targeting
                //Stop();
                return;

            BulletComponent closestBullet = null;
            var closestDistance = Mathf.Infinity;
            foreach (var bullet in flareRegister.bullets)
            {
                var bDist = Vector3.Distance(bullet.transform.position, transform.position);
                if (closestBullet == null || (bDist < bulletSoundRange && bDist < closestDistance))
                {
                    closestBullet = bullet;
                    closestDistance = bDist;
                }
            }

            if (closestBullet != null && closestBullet.DamageRange == 0)
            {
                targetObject.transform.position = closestBullet.transform.position;
                Target(targetObject);
            }
            else
            {
                targetObject.transform.position = transform.position;
                Target(targetObject);
                Stop();
            }
        }
    }
    /*
    STANDARD BEHAVIOR:
        - OnSeeing/Hearing Player:
            - Run towards Player
        - OnHearing Bolt:
            - Run towards bolt
        - OnSeeing Flare:
            - Run towards flare if not within (flareRange + flareMargin).
                - This acts like if you are far enough away that you don't feel the pain of the flare
        - OnSeeing FlareBeacon:
            - Run away if within (flareBeaconRange + flareBeaconMargin).
       
     */

    //Runs towards target. If its meant to be hostile, it will melee attack once in range.
    public void Target(GameObject obj, bool hostile = false)
    {
        if (target == null || target.target != obj)
        {
            //target = new TargetInformation(obj,hostile,false,runTowards:true);
            target = new TargetInformation();
            target.target = obj;
            target.isHostile = hostile;
            target.hasDamageRange = false;
            target.runTowards = true;
            timeSinceTarget = 0;
        }
    }

    //Runs towards flare such that it stops randomly within margin before range.
    public void TargetFlare(GameObject obj, float range, float margin)
    {
        if (target == null || target.target != obj)
        {
            //target = new TargetInformation(obj, false, true, range, margin,runTowards:true);
            target = new TargetInformation();
            target.target = obj;
            target.isHostile = false;
            target.hasDamageRange = true;
            target.damageRange = range;
            target.damageMargin = margin;
            target.runTowards = true;
            timeSinceTarget = 0;
        }
    }

    /// <summary>
    ///     Runs away from object. A minimum range is specified where it no longer takes damage, and a minimum margin is
    ///     speicifed where it can stop running.
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="minRange"></param>
    /// <param name="minMargin"></param>
    public void RunAwayFrom(GameObject obj, float minRange, float minMargin)
    {
        if (target == null || target.target != obj)
        {
            //target = new TargetInformation(obj, false, true, minRange, minMargin, false);
            target = new TargetInformation();
            target.target = obj;
            target.isHostile = false;
            target.hasDamageRange = true;
            target.damageRange = minRange;
            target.damageMargin = minMargin;
            target.runTowards = false;
            oppositeVector = -(target.target.transform.position - transform.position).normalized;
            timeSinceTarget = 0;
        }
    }

    public void Stop()
    {
        target = new TargetInformation();
        target.target = gameObject;
        target.isHostile = false;
        target.hasDamageRange = false;
        target.runTowards = true;
    }

    public void Kill(bool fast = false)
    {
        if (isAlive)
        {
            animator.Kill(fast);
            isAlive = false;
            agent.isStopped = true;
        }
    }
}

internal class TargetInformation
{
    public float damageMargin;
    public float damageRange;
    public bool hasDamageRange;
    public bool isHostile;
    public bool runTowards;

    public GameObject target;
}