using InputSystem;
using UnityEngine;
using UnityEngine.InputSystem;

namespace AsteroidGame
{
    public class CameraController : MonoBehaviour
    {
        private CameraControllActions cameraActions;
        private InputAction movement;
        private Transform cameraTransform;
        private Camera cameraReference;

        // Horizontal Movement
        [SerializeField]
        private float maxSpeed = 5f;
        private float speed;
        [SerializeField]
        private float acceleration = 10f;
        [SerializeField]
        private float damping = 15f;

        //Vertial Motion - Zooming
        [SerializeField]
        private float stepSize = 2f;
        [SerializeField]
        private float zoomDampening = 7.5f;
        [SerializeField]
        private float minHeight = 5f;
        [SerializeField]
        private float maxHeight = 50f;
        [SerializeField]
        private float zoomSpeed = 2f;

        //Rotation
        [SerializeField]
        private float maxRotationSpeed = 1f;

        //Screen Edge Motion
        [SerializeField]
        [Range(0f, 0.1f)]
        private float edgeTolerance = 0.05f;
        [SerializeField]
        private bool useScreenEdge = false;

        private Vector3 targetPosition;
        private float zoomHeight;
        private Vector3 horizontalVelocity;
        private Vector3 lastPosition;
        private Plane cameraPlane;

        Vector3 startDrag;

        private void Awake()
        {
            cameraActions = new CameraControllActions();
            
            cameraReference = this.GetComponentInChildren<Camera>();
            cameraTransform = cameraReference.transform;
        }

        private void OnEnable()
        {
            cameraPlane = new Plane(Vector3.up, Vector3.zero);
            zoomHeight = cameraTransform.localPosition.y;
            cameraTransform.LookAt(this.transform);
            lastPosition = this.transform.position;
            movement = cameraActions.Camera.Movement;
            cameraActions.Camera.RotateCamera.performed += RotateCamera;
            cameraActions.Camera.ZoomCamera.performed += ZoomCamera;
            cameraActions.Camera.Enable();

        }

        private void OnDisable()
        {
            cameraActions.Camera.RotateCamera.performed -= RotateCamera;
            cameraActions.Camera.ZoomCamera.performed -= ZoomCamera;
            cameraActions.Disable();
        }



        private void Update()
        {
            GetKeyboardMovement();
            if (useScreenEdge)
            {
                CheckMouseAtScreenEdge();
            }
            DragCamera();
            UpdateVelocity();
            UpdateCameraPosition();
            UpdateBasePosition();

        }

        private void UpdateVelocity()
        {
            horizontalVelocity = (this.transform.position - lastPosition) / Time.deltaTime;
            horizontalVelocity.y = 0;
            lastPosition = this.transform.position;
        }

        private void GetKeyboardMovement()
        {
            Vector3 inputValue = movement.ReadValue<Vector2>().x * GetCameraRight()
                                + movement.ReadValue<Vector2>().y * GetCameraForward();
            inputValue = inputValue.normalized;

            if (inputValue.sqrMagnitude > 0.1f)
            {
                targetPosition += inputValue;
            }
        }

        private Vector3 GetCameraRight()
        {
            Vector3 right = cameraTransform.right;
            right.y = 0;
            return right;
        }

        private Vector3 GetCameraForward()
        {
            Vector3 forward = cameraTransform.forward;
            forward.y = 0;
            return forward;
        }

        private void UpdateBasePosition()
        {
            if (targetPosition.sqrMagnitude > 0.1f)
            {
                speed = Mathf.Lerp(speed, maxSpeed, Time.deltaTime * acceleration);
                transform.position +=  speed * Time.deltaTime * targetPosition;
            }
            else
            {
                horizontalVelocity = Vector3.Lerp(horizontalVelocity, Vector3.zero, Time.deltaTime * damping);
                transform.position += horizontalVelocity * Time.deltaTime;
            }

            targetPosition = Vector3.zero;
        }

        private void RotateCamera(InputAction.CallbackContext inputValue)
        {
            if (!Mouse.current.middleButton.isPressed) { return; }

            float value = inputValue.ReadValue<Vector2>().x;
            transform.rotation = Quaternion.Euler(0f, value * maxRotationSpeed + transform.rotation.eulerAngles.y, 0f);
        }

        private void ZoomCamera(InputAction.CallbackContext inputValue)
        {
            float value = -inputValue.ReadValue<Vector2>().y / 100f;

            if (Mathf.Abs(value) > 0.1f)
            {
                zoomHeight = cameraTransform.localPosition.y + value * stepSize;
                if (zoomHeight < minHeight)
                    zoomHeight = minHeight;
                else if (zoomHeight > maxHeight)
                    zoomHeight = maxHeight;
            }
        }

        private void UpdateCameraPosition()
        {
            Vector3 zoomTarget = new Vector3(cameraTransform.localPosition.x, zoomHeight, cameraTransform.localPosition.z);
            zoomTarget -= zoomSpeed * (zoomHeight - cameraTransform.localPosition.y) * Vector3.forward;

            cameraTransform.localPosition = Vector3.Lerp(cameraTransform.localPosition, zoomTarget, Time.deltaTime * zoomDampening);
            cameraTransform.LookAt(this.transform);
        }

        private void CheckMouseAtScreenEdge()
        {
            Vector2 mousePosition = Mouse.current.position.ReadValue();
            Vector3 moveDirection = Vector3.zero;

            if (mousePosition.x < edgeTolerance * Screen.width)
                moveDirection += -GetCameraRight();
            else if (mousePosition.x > (1f - edgeTolerance) * Screen.width)
                moveDirection += GetCameraRight();

            if (mousePosition.y < edgeTolerance * Screen.height)
                moveDirection += -GetCameraForward();
            else if (mousePosition.y > (1f - edgeTolerance) * Screen.height)
                moveDirection += GetCameraForward();

            targetPosition += moveDirection;
        }

        private void DragCamera()
        {
            if (!Mouse.current.rightButton.isPressed) { return; }

            
            Ray ray = cameraReference.ScreenPointToRay(Mouse.current.position.ReadValue());
            if (cameraPlane.Raycast(ray,out float distance))
            {
                if (Mouse.current.rightButton.wasPressedThisFrame)
                    startDrag = ray.GetPoint(distance);
                else
                    targetPosition += startDrag - ray.GetPoint(distance);
            }
        }


    }
}
