using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.AI;

namespace GGJ2021
{
    public class EnemyController : MonoBehaviour
    {
        public enum EnemyState
        {
            Patrolling,
            Waiting,
            Chasing,
            Alerted,
            Frozen,
        }

        [HideInInspector]
        public EnemyState State = EnemyState.Patrolling;

        [SerializeField]
        private GameObject[] enemyHeads = null;

        [SerializeField]
        private GameObject[] enemyBodies = null;

        [SerializeField]
        private Waypoint[] waypoints = null;

        [SerializeField]
        [Range(1f, 30f)]
        private float seeRange = 12f;

        [SerializeField]
        private LayerMask seeableLayers = default;

        [SerializeField]
        [Range(10f, 90f)]
        private float halfFOV = 30f;

        [SerializeField]
        private float alertIncreaseSpeed = 15f;

        [SerializeField]
        private float alertDecreaseSpeed = 10f;

        [SerializeField]
        private float decreaseAlertAfterDelayDuration = 2f;

        [SerializeField]
        [Range(0f, 100f)]
        private float startChasingLevel = 20f;

        [SerializeField]
        [Range(0f, 100f)]
        private float stopChasingLevel = 10f;

        [SerializeField]
        private Transform questionMark = null;

        [Header("SFX")]
        [SerializeField]
        private FMODUnity.StudioEventEmitter maybeAlertedSfx = null;

        [SerializeField]
        private FMODUnity.StudioEventEmitter alertedSfx = null;

        // [SerializeField]
        // private TMPro.TextMeshProUGUI alertLevelDebugText = null;

        private Transform _playerT;
        private PlayerCamouflage _playerCamouflage;
        private Animator _animator;
        private int _currentWaypointIdx;
        private NavMeshAgent _agent;
        private float _waitTimeLeft;
        private float _timeAtLastSeePlayer;
        private float _alertLevel; // 0 - 100
        private int _overrideNextWaypointIdx;
        private Vector3 _startingQuestionMarkScale;

        private void Awake()
        {
            _agent = GetComponent<NavMeshAgent>();
            _animator = GetComponentInChildren<Animator>();

            State = EnemyState.Patrolling;
            _currentWaypointIdx = 0;
            _overrideNextWaypointIdx = 0;
            _currentWaypointIdx = -1;
            _timeAtLastSeePlayer = -Mathf.Infinity;

            SelectHeadAndBody();
        }

        private void SelectHeadAndBody()
        {
            int randomHeadIdx = Random.Range(0, enemyHeads.Length);
            int randomBodyIdx = Random.Range(0, enemyBodies.Length);

            for (int i = 0; i < enemyHeads.Length; i++)
                enemyHeads[i].SetActive(i == randomHeadIdx);

            for (int i = 0; i < enemyBodies.Length; i++)
                enemyBodies[i].SetActive(i == randomBodyIdx);
        }

        private void Start()
        {
            _startingQuestionMarkScale = questionMark.transform.localScale;
            questionMark.transform.localScale = Vector3.zero;

            _playerCamouflage = FindObjectOfType<PlayerCamouflage>();
            _playerT = _playerCamouflage.transform;

            GoToNextPoint();
        }

        private void Update()
        {
            _animator.SetFloat("Speed", _agent.velocity.magnitude);

            switch (State)
            {
                case EnemyState.Patrolling:
                    {
                        UpdatePatrolling();
                        CheckIfSeesPlayer();
                        CheckIfShouldChasePlayer();
                        break;
                    }
                case EnemyState.Waiting:
                    {
                        UpdateWaiting();
                        CheckIfSeesPlayer();
                        CheckIfShouldChasePlayer();
                        break;
                    }
                case EnemyState.Chasing:
                    {
                        CheckIfSeesPlayer();
                        Chase();
                        break;
                    }
                case EnemyState.Frozen:
                    {
                        break;
                    }
                case EnemyState.Alerted:
                    {
                        break;
                    }
            }

            // DEBUG
            // if (alertLevelDebugText != null)
            // {
            //     alertLevelDebugText.text = Mathf.Round(_alertLevel).ToString();
            //     if (State == EnemyState.Patrolling || State == EnemyState.Waiting)
            //     {
            //         alertLevelDebugText.color = Color.green;
            //     }
            //     else if (State == EnemyState.Chasing)
            //     {
            //         alertLevelDebugText.color = Color.yellow;
            //     }
            //     else if (State == EnemyState.Alerted)
            //     {
            //         alertLevelDebugText.color = Color.red;
            //     }
            // }
        }

        public void Disable()
        {
            State = EnemyState.Frozen;
            _agent.enabled = false;
            questionMark.gameObject.SetActive(false);
        }

        private void CheckIfSeesPlayer()
        {
            var dirToPlayer = _playerT.position - transform.position;
            var angleToFacePlayer = Vector3.Angle(transform.forward, new Vector3(dirToPlayer.x, 0f, dirToPlayer.z));

            if (!_playerCamouflage.IsHidden && angleToFacePlayer < halfFOV)
            {
                var startPos = transform.position + Vector3.up;
                var target = _playerT.position + Vector3.up; // move a little off the ground
                var targetDir = target - startPos;
                // Debug.DrawRay(startPos, targetDir);

                // Make it so if player is in front of enemy, the range is shortened to make it feel more fair
                var adjustedSeeRange = transform.position.z < _playerT.position.z ? seeRange * 0.5f : seeRange;

                // Check if can see
                if (Physics.Raycast(startPos, targetDir, out RaycastHit hitInfo, seeRange, seeableLayers))
                {
                    // Hit something... see if it's the player
                    if (hitInfo.transform.CompareTag("Player"))
                    {
                        _timeAtLastSeePlayer = Time.time;
                        _alertLevel += alertIncreaseSpeed * Time.deltaTime;
                        _alertLevel = Mathf.Clamp(_alertLevel, 0f, 100f);
                    }
                }
            }
            else if (_timeAtLastSeePlayer + decreaseAlertAfterDelayDuration < Time.time)
            {
                // If enemy hasn't seen player in a couple seconds
                _alertLevel -= alertDecreaseSpeed * Time.deltaTime;
                _alertLevel = Mathf.Clamp(_alertLevel, 0f, 100f);
            }

            if (_alertLevel >= 100)
            {
                State = EnemyState.Alerted;
                _animator.SetBool("Cautious", false);
                _animator.SetTrigger("Lose");
                _agent.isStopped = true;
                alertedSfx.Play();
                Messenger.Broadcast(Events.OnGameLose);
            }
        }

        private void UpdatePatrolling()
        {
            if (!_agent.pathPending && _agent.remainingDistance < 0.5f)
            {
                var currentWaypoint = waypoints[_currentWaypointIdx];

                if (currentWaypoint.WaitTime > 0f)
                {
                    // Wait
                    State = EnemyState.Waiting;
                    _waitTimeLeft = currentWaypoint.WaitTime;
                }
                else
                {
                    // Go immediately to next waypoint
                    GoToNextPoint();
                }
            }
        }

        private void UpdateWaiting()
        {
            _waitTimeLeft -= Time.deltaTime;

            if (_waitTimeLeft <= 0)
            {
                State = EnemyState.Patrolling;
                GoToNextPoint();
            }
        }

        private void GoToNextPoint()
        {
            // Returns if no points have been set up
            if (waypoints.Length == 0 || !_agent.enabled)
                return;

            // Choose the next point in the array as the destination,
            // cycling to the start if necessary.
            _currentWaypointIdx = _overrideNextWaypointIdx >= 0 ? _overrideNextWaypointIdx : (_currentWaypointIdx + 1) % waypoints.Length;
            _overrideNextWaypointIdx = -1;

            // Set the agent to go to the currently selected destination.
            _agent.stoppingDistance = 0f;
            _agent.destination = waypoints[_currentWaypointIdx].transform.position;
        }

        private void CheckIfShouldChasePlayer()
        {
            if (_alertLevel > startChasingLevel)
            {
                // Start chasing player
                State = EnemyState.Chasing;
                questionMark.DOScale(_startingQuestionMarkScale, 0.5f).SetEase(Ease.OutCubic);
                _animator.SetBool("Cautious", true);
                maybeAlertedSfx.Play();
                _animator.SetTrigger("Startle");
                StartCoroutine(TemporarilyPauseAgent());
            }
        }

        private IEnumerator TemporarilyPauseAgent()
        {
            _agent.isStopped = true;
            yield return new WaitForSeconds(0.5f);
            _agent.isStopped = false;
        }

        private void Chase()
        {
            if (!_agent.pathPending && _agent.enabled)
            {
                _agent.stoppingDistance = 2f;
                _agent.destination = _playerT.position;
            }

            CheckIfShouldContinueChase();
        }

        private void CheckIfShouldContinueChase()
        {
            if (_alertLevel < stopChasingLevel)
            {
                // Stop chasing player
                State = EnemyState.Patrolling;
                _animator.SetBool("Cautious", false);
                _overrideNextWaypointIdx = GetClosestWaypoint();
                questionMark.DOScale(Vector3.zero, 0.75f).SetEase(Ease.OutCubic);
                GoToNextPoint();
            }
        }

        private int GetClosestWaypoint()
        {
            int nearestIdx = 0;
            float minDist = Mathf.Infinity;
            Vector3 currentPos = transform.position;

            for (int i = 0; i < waypoints.Length; i++)
            {
                var wp = waypoints[i].transform;
                float dist = Vector3.Distance(wp.position, currentPos);
                if (dist < minDist)
                {
                    nearestIdx = i;
                    minDist = dist;
                }
            }

            return nearestIdx;
        }

        private void OnDrawGizmosSelected()
        {
            Gizmos.color = Color.cyan;
            int segments = 10;
            float segmentAngle = halfFOV * 2 / segments;

            if (_playerT == null)
                _playerT = FindObjectOfType<PlayerCamouflage>().transform;

            var adjustedSeeRange = transform.position.z < _playerT.position.z ? seeRange * 0.5f : seeRange;

            for (float angle = -halfFOV; angle <= halfFOV; angle += segmentAngle)
            {
                Gizmos.DrawLine(
                    transform.position,
                    transform.position + Quaternion.Euler(0, angle, 0) * transform.forward * adjustedSeeRange
                );
            }
        }
    }
}
