using UnityEngine;
using Mirror;
using TMPro;
using Characters;
using Debuffs;
using Koptilnya;

/* Note: animations are called via the controller for both the character and capsule using animator null checks
 */

namespace StarterAssets
{
    [RequireComponent(typeof(CharacterController))]

    public class PersonController : Pawn
    {
        [Header("Oscar nasral v kod")]
        public GameObject headPosition;
        public GameObject floatingInfo;
        public TMP_Text nicknameInput;
        public AudioSource audioSource;
        public AudioClip footStepClip;
        private AudioClip _gotEnergyClip;

        private Interactions _interactions;

        private Vector2 _lerpInputVec, _inputVec;
        private float _controllerVelocity, lerp;

        private bool _inputJump;

        private Vector2 _inputMove;
        public float _stepCycle, _stepTiming = 1;

        [Header("Player")]
        [Tooltip("Move speed of the character in m/s")]
        public float MoveSpeed = 2.0f;
        [Tooltip("Sprint speed of the character in m/s")]
        public float SprintSpeed = 5.335f;

        [Tooltip("Acceleration and deceleration")]
        public float SpeedChangeRate = 10.0f;

        [Space(10)]
        [Tooltip("The height the player can jump")]
        public float JumpHeight = 1.2f;
        [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
        public float Gravity = -15.0f;

        [Space(10)]
        [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
        public float JumpTimeout = 0.50f;
        [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
        public float FallTimeout = 0.15f;

        [Header("Player Grounded")]
        [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
        public bool Grounded = true;
        [Tooltip("Useful for rough ground")]
        public float GroundedOffset = -0.14f;
        [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
        public float GroundedRadius = 0.28f;
        [Tooltip("What layers the character uses as ground")]
        public LayerMask GroundLayers;

        public bool LockCameraPosition = false;

        private float _cinemachineTargetYaw;

        [SyncVar]
        public Transform _parent;

        private float _speed;
        private float _animationBlend;

        private float _verticalVelocity;
        private float _terminalVelocity = 53.0f;

        private float _jumpTimeoutDelta;
        private float _fallTimeoutDelta;

        // animation IDs
        private int _animIDSpeed, _animIDGrounded, _animIDJump, _animIDFreeFall, _animIDMotionSpeed, _animIDX, _animIDY;

        public Animator _animator;
        private CharacterController _controller;
        public GameObject mainCamera;

        private const float _threshold = 0.01f;
        private bool _hasAnimator = true;

        [SyncVar(hook = nameof(OnPlayerSetup))]
        public string playerName;

        [SyncVar, HideInInspector]
        public Color gunColor;

        private Vector3 _position;
        
        private float _rotationVelocity;
        private Vector2 _mouseLook;
        [SyncVar]
        public float _cinemachineTargetPitch;
        public GameObject CinemachineCameraTarget;
        public float RotationSpeed = 1f;
        public float BottomClamp = -90.0f;
        public float TopClamp = 90.0f;


        [Command]
        public void CmdSetupPlayer(string _name, float r, float g, float b)
        {
            gunColor = new Color(r, g, b);

            playerName = _name;
        }

        public void OnPlayerSetup(string _Old, string _New)
        {
            nicknameInput.text = playerName;
        }

        public override void OnStartLocalPlayer()
        {
            CmdSetupPlayer(
                PlayerPrefs.GetString("NickName"),
                PlayerPrefs.GetFloat("GunColorR"),
                PlayerPrefs.GetFloat("GunColorG"),
                PlayerPrefs.GetFloat("GunColorB")
            );
        }

        private void Start()
        {
            RotationSpeed = PlayerPrefs.GetFloat("Sensitivity");
            _controller = GetComponent<CharacterController>();

            _gotEnergyClip = Resources.Load<AudioClip>("Audio/CharacterSounds/GetEnergy");

            Live();

            _interactions = GetComponent<Interactions>();

            if (isLocalPlayer)
            {
                floatingInfo.SetActive(false);
            }

            if (!isLocalPlayer) return;

            AssignAnimationIDs();

            _jumpTimeoutDelta = JumpTimeout;
            _fallTimeoutDelta = FallTimeout;
        }

        private void Update()
        {
            if (Alive)
            {
                CalculateFootstepTriggers();
                setParent();
            }

            if (!isLocalPlayer)
            {
                if (netIdentity.isActiveAndEnabled && floatingInfo != null && Camera.main)
                    floatingInfo.transform.LookAt(Camera.main.transform);
                return;
            }

            if (Cursor.lockState == CursorLockMode.None) return;

            if (Alive)
            {
                CalculateEnergy();
                GroundedCheck();
                JumpAndGravity();
                Move();
            }
            else
            {
                SpectatorMove();
            }
        }

        private void LateUpdate()
        {
            if (!isLocalPlayer) return;
            if (Cursor.lockState == CursorLockMode.None) return;
            CameraRotation();
            SetCamToHeadPos();
        }
        
        void CameraRotation()
        {
            if (Cursor.lockState == CursorLockMode.None) return;

            _mouseLook = new Vector2(Input.GetAxis("Mouse X"), -Input.GetAxis("Mouse Y"));
            _cinemachineTargetPitch += _mouseLook.y * RotationSpeed;
            _rotationVelocity = _mouseLook.x * RotationSpeed;

            _cinemachineTargetPitch = _cinemachineTargetPitch.ClampAngle(BottomClamp, TopClamp);

            transform.Rotate(Vector3.up * _rotationVelocity);

            CinemachineCameraTarget.transform.localRotation = Quaternion.Euler(_cinemachineTargetPitch, 0.0f, 0.0f);
            // CmdSetCameraPitch(_cinemachineTargetPitch);
        }

        private void SetCamToHeadPos()
        {
            CinemachineCameraTarget.transform.position = headPosition.transform.position;
        }

        private void AssignAnimationIDs()
        {
            _animIDSpeed = Animator.StringToHash("Speed");
            _animIDGrounded = Animator.StringToHash("Grounded");
            _animIDJump = Animator.StringToHash("Jump");
            _animIDFreeFall = Animator.StringToHash("FreeFall");
            _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
            _animIDX = Animator.StringToHash("x");
            _animIDY = Animator.StringToHash("y");
        }

        private void GroundedCheck()
        {
            Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
            Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);

            if (_hasAnimator)
            {
                _animator.SetBool(_animIDGrounded, Grounded);
            }
        }

        public float currentEnergy = 100f, shootDepletion = 15f, _flashLightDepletion = 0.4f;
        private float _maxEnergy = 300f, _depletionSpeed = 0.5f, _regenerationSpeed = 1f, _jumpDepletion = 5f;
        private void CalculateEnergy()
        {
            if (Input.GetKey(KeyCode.LeftShift) && _inputMove.magnitude != 0)
            {
                currentEnergy = Mathf.MoveTowards(currentEnergy, 0, _depletionSpeed * Time.deltaTime);
            }
            else if (currentEnergy <= _maxEnergy)
            {
                currentEnergy = Mathf.MoveTowards(currentEnergy, _maxEnergy, _regenerationSpeed * Time.deltaTime);
            }

            if (currentEnergy <= 0)
            {
                CmdDie();
            }
        }
        public void InstantStaminaReduction(float Reduction)
        {
            currentEnergy = currentEnergy -= Reduction;
        }
        public void InstantStaminaRegeneration(float Regeneration)
        {
            currentEnergy = currentEnergy += Regeneration;
            CmdGotEnergy();
        }

        [Command(requiresAuthority = false)]
        void CmdGotEnergy()
        {
            RpcGotEnergy();
        }

        [ClientRpc]
        void RpcGotEnergy()
        {
            audioSource.PlayOneShot(_gotEnergyClip);
        }

        [Command]
        void CmdSetCameraPitch(float value)
        {
            _cinemachineTargetPitch = value;
        }

        void SetCameraPitch()
        {
            lerp = Mathf.Lerp(lerp, _cinemachineTargetPitch, Time.deltaTime * 10f);

            CinemachineCameraTarget.transform.localRotation = Quaternion.Euler(lerp, 0.0f, 0.0f);
        }

        private void Move()
        {
            _inputMove = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));

            float targetSpeed = Input.GetKey(KeyCode.LeftShift) ? (currentEnergy <= _depletionSpeed ? MoveSpeed : SprintSpeed) : MoveSpeed;
            targetSpeed *= _speedMul;

            if (_inputMove == Vector2.zero) targetSpeed = 0.0f;

            float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

            float speedOffset = 0.1f;
            float inputMagnitude = 1f;

            if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
            {
                _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);

                _speed = Mathf.Round(_speed * 1000f) / 1000f;
            }
            else
            {
                _speed = targetSpeed;
            }
            _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);


            Vector3 inputDirection = new Vector3(_inputMove.x, 0.0f, _inputMove.y).normalized;

            if (_inputMove != Vector2.zero)
            {
                inputDirection = transform.right * _inputMove.x + transform.forward * _inputMove.y;
            }

            RaycastHit hit;

            if (Physics.Raycast(transform.position, -transform.up, out hit, 1f, LayerMask.NameToLayer("Player")))
            {
                if (hit.collider.tag == "MovingPlatform" && hit.collider.GetComponentInParent<NetworkIdentity>() != null)
                {
                    CmdSetParent(hit.collider.transform.parent);
                }
                else
                {
                    CmdSetParent(null);
                }
            }

            _controller.Move(inputDirection.normalized * (_speed * Time.deltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

            if (_hasAnimator)
            {
                _lerpInputVec = Vector2.Lerp(_lerpInputVec, _inputMove, Time.deltaTime * 10);

                _animator.SetFloat(_animIDSpeed, _animationBlend);
                _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);

                _animator.SetFloat(_animIDX, _lerpInputVec.x);
                _animator.SetFloat(_animIDY, _lerpInputVec.y);
            }
        }

        void SpectatorMove()
        {
            float inputZ = (Input.GetKey(KeyCode.E) ? 1 : 0) - (Input.GetKey(KeyCode.Q) ? 1 : 0) + (Input.GetKey(KeyCode.Space) ? 1 : 0) - (Input.GetKey(KeyCode.LeftShift) ? 1 : 0);

            float targetSpeed = MoveSpeed; //Input.GetKey(KeyCode.LeftShift) ? SprintSpeed : MoveSpeed;

            _inputMove = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));

            Vector3 inputDirection = transform.right * _inputMove.x + transform.forward * _inputMove.y + transform.up * inputZ;


            transform.position += inputDirection * Time.deltaTime * targetSpeed * 10f;
        }

        void setParent()
        {
            transform.parent = _parent;
        }

        [Command]
        void CmdSetParent(Transform transf)
        {
            _parent = transf;
        }

        public override void TakeDamage()
        {
            _animator.SetTrigger("damage");
            InstantStaminaReduction(shootDepletion);
            // Debug.Log(self.GetComponent<PersonController>().playerName);

            // hitVFX.Stop();
            // hitVFX.Play();

            // self.GetComponent<Interactions>().DropProp();
        }

        private void JumpAndGravity()
        {
            if (Grounded)
            {
                _inputJump = Input.GetKeyDown(KeyCode.Space);
                // reset the fall timeout timer
                _fallTimeoutDelta = FallTimeout;

                // update animator if using character
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDJump, false);
                    _animator.SetBool(_animIDFreeFall, false);
                }

                // stop our velocity dropping infinitely when grounded
                if (_verticalVelocity < 0.0f)
                {
                    _verticalVelocity = -2f;
                }

                // Jump
                if (_inputJump && _jumpTimeoutDelta <= 0.0f && currentEnergy >= _jumpDepletion)
                {
                    // the square root of H * -2 * G = how much velocity needed to reach desired height
                    _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                    InstantStaminaReduction(_jumpDepletion);

                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDJump, true);
                    }
                }

                // jump timeout
                if (_jumpTimeoutDelta >= 0.0f)
                {
                    _jumpTimeoutDelta -= Time.deltaTime;
                }
            }
            else
            {
                // reset the jump timeout timer
                _jumpTimeoutDelta = JumpTimeout;

                // fall timeout
                if (_fallTimeoutDelta >= 0.0f)
                {
                    _fallTimeoutDelta -= Time.deltaTime;
                }
                else
                {
                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDFreeFall, true);
                    }
                }

                // if we are not grounded, do not jump
                _inputJump = false;
            }

            // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
            if (_verticalVelocity < _terminalVelocity)
            {
                _verticalVelocity += Gravity * Time.deltaTime;
            }
        }
        
        private void OnDrawGizmosSelected()
        {
            Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
            Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

            if (Grounded) Gizmos.color = transparentGreen;
            else Gizmos.color = transparentRed;

            // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
            Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
        }
        
        void CalculateFootstepTriggers()
        {
            float _controllerVelocity = ((_position - transform.position) / Time.deltaTime).magnitude;
            // Debug.Log(_controllerVelocity);
            float clampedVelocity = Mathf.Clamp(SprintSpeed / _controllerVelocity + 1, 1, SprintSpeed);

            if (Time.time > _stepCycle && Grounded && _controllerVelocity > 0.1)
            {
                CallFootstepClip();

                _stepCycle = Time.time + (clampedVelocity * _stepTiming);
            }

            _position = transform.position;
        }

        public void CallFootstepClip()
        {
            audioSource.pitch = 1f + Random.Range(-0.1f, 0.1f);
            audioSource.PlayOneShot(footStepClip);
        }
    }
}