using System;
using Core.Domain.Status;
using Core.Domain.Status.Effects;
using Core.Ports;
using KBCore.Refs;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Infrastructure.Unity
{
    [RequireComponent(typeof(Rigidbody))]
    public class PlayerController : MonoBehaviour
    {
        [Header("Movement Settings")]
        [SerializeField]
        private float moveSpeed = 8f;
        [SerializeField] private float maxVelocityChange = 10f;
        [SerializeField] private float snapForce = 15f;
        
        [Header("Jump Settings")]
        [SerializeField] private float jumpForce = 12f; // Adjusted for snappy feel
        [SerializeField] private float coyoteTime = 0.1f; // Grace period after falling
        [SerializeField] private float gravityMultiplier = 2.5f; // Stronger gravity for less "floaty" feel

        [Header("Controls")]
        [SerializeField] private bool useCameraRelativeMovement = true;

        [Header("Interaction")]
        [SerializeField] private LayerMask tileLayer;
        [SerializeField] private float groundCheckDistance = 1.1f; // Slightly more than half player height
        
        [Self][SerializeField] private Rigidbody rb;
        [Self][SerializeField] private MeshRenderer meshRenderer;

        private InputSystem_Actions _actions;
        private Vector2 _moveInput;
        private Transform _camTransform;
        
        private MaterialPropertyBlock _propBlock;
        private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor");
        private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor");
        private Color _defaultColor = Color.white;

        private bool _isGrounded;
        private float _coyoteTimeCounter;
        private bool _jumpPressed;
        private Vector3 _currentTileCentroid;
        
        public Rigidbody Rigidbody => rb;
        public StatusManager Status { get; private set; }

        private void OnEnable()
        {
            _actions.Player.Enable();

            _actions.Player.Move.performed += OnMovePerformed;
            _actions.Player.Move.canceled += OnMoveCanceled;
            _actions.Player.Jump.performed += OnJumpPerformed;
        }

        private void OnDisable()
        {
            _actions.Player.Move.performed -= OnMovePerformed;
            _actions.Player.Move.canceled -= OnMoveCanceled;
            _actions.Player.Jump.performed -= OnJumpPerformed;

            _actions.Player.Disable();
        }

        private void Awake()
        {
            _actions = new InputSystem_Actions();
            Status = new StatusManager();

            if (Camera.main)
            {
                _camTransform = Camera.main.transform;
            }

            rb.freezeRotation = true;
            rb.useGravity = false;
            
            _propBlock = new MaterialPropertyBlock();
            if (meshRenderer.material.HasProperty(ColorProperty))
            {
                _defaultColor = meshRenderer.material.GetColor(ColorProperty);
            }
        }

        private void Update()
        {
            Status.Tick(Time.deltaTime);
            UpdateVisuals();

            if (_isGrounded)
            {
                _coyoteTimeCounter = coyoteTime;
            }
            else
            {
                _coyoteTimeCounter -= Time.deltaTime;
            }

            if (_jumpPressed)
            {
                _jumpPressed = false;
                if (_coyoteTimeCounter > 0f || Status.CurrentCapabilities.CanHover)
                {
                    PerformJump();
                }
            }

        }

        private void FixedUpdate()
        {
            CheckGround();
            HandleMovement();
            ApplyGravity();
            
            // Interaction with tiles (Decay Logic)
            if (_isGrounded && Status.CurrentCapabilities.CanTriggerDecay)
            {
                InteractWithGround();
            }
        }
        
        private void CheckGround()
        {
            if (Physics.SphereCast(transform.position, 0.3f, Vector3.down, out var hit, groundCheckDistance, tileLayer))
            {
                _isGrounded = true;
                _currentTileCentroid = hit.collider.transform.position;
            }
            else
            {
                _isGrounded = false;
            }
        }

        private void PerformJump()
        {
            var vel = rb.linearVelocity;
            vel.y = 0;
            rb.linearVelocity = vel;

            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
            
            _coyoteTimeCounter = 0f;
            _isGrounded = false; 
        }

        private void ApplyGravity()
        {
            if (Status.CurrentCapabilities.CanHover) return;

            var gravity = Physics.gravity.y * gravityMultiplier;
            rb.AddForce(Vector3.up * gravity, ForceMode.Acceleration);
        }

        private void HandleMovement()
        {
            var currentSpeed = moveSpeed * Status.CurrentCapabilities.SpeedMultiplier;
            var targetVelocity = Vector3.zero;
            var snapAxis = Vector3.zero;

            Vector3 desiredDirection;

            if (_moveInput.sqrMagnitude < 0.01f)
            {
                desiredDirection = Vector3.zero;
            }
            else if (useCameraRelativeMovement && _camTransform)
            {
                var camForward = _camTransform.forward;
                var camRight = _camTransform.right;
                camForward.y = 0;
                camRight.y = 0;
                camForward.Normalize();
                camRight.Normalize();

                desiredDirection = (camForward * _moveInput.y + camRight * _moveInput.x).normalized;
            }
            else
            {
                desiredDirection = new Vector3(_moveInput.x, 0, _moveInput.y).normalized;
            }


            if (desiredDirection.sqrMagnitude > 0.01f)
            {
                if (Mathf.Abs(desiredDirection.x) > Mathf.Abs(desiredDirection.z))
                {
                    targetVelocity = new Vector3(Mathf.Sign(desiredDirection.x) * currentSpeed, 0, 0);
                    snapAxis = Vector3.forward;
                }
                else
                {
                    targetVelocity = new Vector3(0, 0, Mathf.Sign(desiredDirection.z) * currentSpeed);
                    snapAxis = Vector3.right;
                }
            }

            var velocity = rb.linearVelocity;
            var velocityChange = (targetVelocity - velocity);

            velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
            velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
            velocityChange.y = 0f;

            rb.AddForce(velocityChange, ForceMode.VelocityChange);

            if (snapAxis != Vector3.zero)
            {
                ApplySnapping(snapAxis);
            }
            else
            {
                ApplySnapping(Vector3.right);
                ApplySnapping(Vector3.forward);
            }
        }

        private void ApplySnapping(Vector3 axis)
        {
            if (!_isGrounded) return;
            
            var targetPos = Vector3.Dot(_currentTileCentroid, axis);
            var currentPos = Vector3.Dot(transform.position, axis);
            var error = targetPos - currentPos;
            
            if (Mathf.Abs(error) < 0.05f) return;

            var currentVelOnAxis = Vector3.Dot(rb.linearVelocity, axis);
            var desiredVel = error * snapForce;
            
            var maxCatchupSpeed = Mathf.Abs(error) / Time.fixedDeltaTime;
            desiredVel = Mathf.Clamp(desiredVel, -maxCatchupSpeed, maxCatchupSpeed);

            var correction = axis * (desiredVel - currentVelOnAxis);
            rb.AddForce(correction, ForceMode.VelocityChange);
        }

        private void InteractWithGround()
        {
            if (Physics.SphereCast(transform.position, 0.3f, Vector3.down, out var hit, groundCheckDistance, tileLayer))
            {
                if (hit.collider.TryGetComponent<ITileView>(out var tileView))
                {
                    tileView.StepOn();
                }
            }
        }

        private void OnMovePerformed(InputAction.CallbackContext ctx)
        {
            _moveInput = ctx.ReadValue<Vector2>();
        }

        private void OnMoveCanceled(InputAction.CallbackContext ctx)
        {
            _moveInput = Vector2.zero;
        }
        
        private void OnJumpPerformed(InputAction.CallbackContext ctx)
        {
            if (ctx.performed) _jumpPressed = true;
        }

        private void UpdateVisuals()
        {
            var targetColor = _defaultColor;
            var emissionColor = Color.black;

            var caps = Status.CurrentCapabilities;

            if (caps.CanHover) 
            {
                targetColor = EffectColors.HoverColor;
                emissionColor = EffectColors.HoverColor * 0.5f; 
            }
            else if (!caps.CanTriggerDecay) 
            {
                targetColor = EffectColors.LightFootedColor;
            }
            else if (caps.SpeedMultiplier > 1.2f)
            {
                targetColor = EffectColors.SpeedBoostColor;
                emissionColor = EffectColors.SpeedBoostColor * 0.5f;
            }

            meshRenderer.GetPropertyBlock(_propBlock);
            
            var currentColor = _propBlock.GetColor(ColorProperty);
            if (currentColor.a == 0) currentColor = _defaultColor; 
            
            var newColor = Color.Lerp(currentColor, targetColor, Time.deltaTime * 5f);
            
            _propBlock.SetColor(ColorProperty, newColor);
            _propBlock.SetColor(EmissionColorProperty, emissionColor);
            
            meshRenderer.SetPropertyBlock(_propBlock);
        }
    }
}