using Unity.Netcode;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Jankenbots.Prototype
{
    /// <summary>
    /// JANKENBOTS — networked run-around PLAYER avatar (the rigged low-poly
    /// character), PLUS the on-foot ↔ driving control handoff.
    ///
    /// CONTROL MODEL (the spine): a player is in exactly one mode, derived from a
    /// single fact — do I currently hold a bot seat? (<see cref="SeatManager"/>).
    ///   • ON FOOT  (no seat)     → WASD drives THIS avatar; free-roaming.
    ///   • DRIVING  (holds a seat)→ avatar TELEPORTS to its player stand and FREEZES;
    ///                              it refuses all input, so control transfers and
    ///                              locks to the claimed tread (TreadPart already only
    ///                              listens when you hold its seat). Release the seat →
    ///                              teleport back and resume on-foot control.
    /// This is what resolves the WASD overlap: PlayerAvatar and TreadPart become
    /// mutually exclusive off one flag — never both live at once.
    ///
    /// NETCODE: OWNER-authoritative movement (see <see cref="OwnerAuthNetworkTransform"/>).
    /// Teleports use NetworkTransform.Teleport so observers don't see a slide.
    ///
    /// ANIMATION: derived locally on every client from measured horizontal speed →
    /// Animator "Speed" → Idle/Walk/Run blend (stays in sync with no extra traffic).
    /// </summary>
    [RequireComponent(typeof(CharacterController))]
    public class PlayerAvatar : NetworkBehaviour
    {
        [Header("Movement")]
        [Tooltip("Run speed in m/s.")]
        public float moveSpeed = 5f;

        [Tooltip("How fast the avatar ramps UP to full speed (m/s^2). Lower = floatier start.")]
        public float acceleration = 45f;

        [Tooltip("How fast the avatar ramps DOWN to a stop (m/s^2). Higher = snappier stop.")]
        public float deceleration = 60f;

        [Tooltip("How fast the avatar turns to face its movement direction (deg/s).")]
        public float turnSpeed = 720f;

        [Tooltip("Gravity (m/s^2, negative). Keeps the CharacterController grounded.")]
        public float gravity = -20f;

        CharacterController _cc;
        Animator _anim;
        NetworkTransform _nt;
        SeatManager _seats;
        Transform _standLeft, _standRight;   // where each pilot teleports (found by name)

        float _vy;                 // vertical velocity (gravity)
        Vector3 _horizVel;         // smoothed horizontal velocity (ramped, not instant)
        Vector3 _lastPos;
        float _animSpeed;

        bool _seated;              // am I (owner) currently piloting a tread?
        Vector3 _returnPos;        // where to drop me back when I leave the seat
        bool _hasReturn;

        static readonly int SpeedHash = Animator.StringToHash("Speed");

        /// <summary>True while the LOCAL owner is seated/driving — read by the camera director.</summary>
        public bool IsSeatedLocal => _seated;

        void Awake()
        {
            _cc = GetComponent<CharacterController>();
            _anim = GetComponentInChildren<Animator>();
            _nt = GetComponent<NetworkTransform>();
            _lastPos = transform.position;
        }

        public override void OnNetworkSpawn()
        {
            _lastPos = transform.position;
            _seats = FindFirstObjectByType<SeatManager>();
            var sl = GameObject.Find("StandPoint_0"); if (sl != null) _standLeft = sl.transform;
            var sr = GameObject.Find("StandPoint_1"); if (sr != null) _standRight = sr.transform;

            // Fan owners out a little so multiple players don't stack on the origin.
            if (IsOwner)
            {
                float a = OwnerClientId * 1.3f;
                transform.position += new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a)) * (2f + OwnerClientId);
                _lastPos = transform.position;
            }
        }

        void Update()
        {
            if (!IsSpawned) return;

            if (IsOwner)
            {
                // Derive mode from seat ownership and handle the transition.
                SeatManager.Seat? seat = LocalSeat();
                bool wantSeated = seat.HasValue;
                if (wantSeated != _seated)
                {
                    if (wantSeated) EnterSeated(seat.Value);
                    else            ExitSeated();
                }

                // Claim / leave a tread by KEY. On-foot mouse-look locks the cursor, so
                // the OnGUI Claim/Leave buttons aren't clickable — E claims, Q leaves.
                var kb = Keyboard.current;
                if (_seats != null && kb != null)
                {
                    if (!_seated && kb.eKey.wasPressedThisFrame) _seats.ClaimSeatRpc();
                    else if (_seated && kb.qKey.wasPressedThisFrame) _seats.ReleaseSeatRpc();
                }

                // On foot → drive the avatar. Seated → do nothing (control is on the tread).
                if (!_seated) OwnerMove();
            }

            // Animation on EVERY client, from measured horizontal speed.
            Vector3 delta = transform.position - _lastPos;
            delta.y = 0f;
            float measured = delta.magnitude / Mathf.Max(Time.deltaTime, 1e-4f);
            _lastPos = transform.position;
            _animSpeed = Mathf.Lerp(_animSpeed, measured, 12f * Time.deltaTime);
            if (_anim != null) _anim.SetFloat(SpeedHash, _animSpeed);
        }

        // Which seat (if any) the local player holds — the single source of truth for mode.
        SeatManager.Seat? LocalSeat()
        {
            if (_seats == null || NetworkManager.Singleton == null) return null;
            ulong me = NetworkManager.Singleton.LocalClientId;
            if (_seats.PilotOf(SeatManager.Seat.Left)  == me) return SeatManager.Seat.Left;
            if (_seats.PilotOf(SeatManager.Seat.Right) == me) return SeatManager.Seat.Right;
            return null;
        }

        void EnterSeated(SeatManager.Seat seat)
        {
            _seated = true;
            _returnPos = transform.position;   // remember where I left the arena
            _hasReturn = true;
            _horizVel = Vector3.zero; _vy = 0f;

            Transform sp = seat == SeatManager.Seat.Left ? _standLeft : _standRight;
            if (sp != null) TeleportTo(sp.position, sp.rotation);
        }

        void ExitSeated()
        {
            _seated = false;
            _horizVel = Vector3.zero; _vy = 0f;
            if (_hasReturn) TeleportTo(_returnPos + Vector3.up * 0.2f, transform.rotation);
        }

        // Interpolation-free teleport (CharacterController must be off to move it directly;
        // NetworkTransform.Teleport keeps observers from seeing a slide across the map).
        void TeleportTo(Vector3 pos, Quaternion rot)
        {
            bool ccWas = _cc.enabled;
            _cc.enabled = false;
            transform.SetPositionAndRotation(pos, rot);
            _cc.enabled = ccWas;
            _lastPos = pos;
            if (_nt != null) _nt.Teleport(pos, rot, transform.localScale);
        }

        void OwnerMove()
        {
            var kb = Keyboard.current;
            float h = 0f, v = 0f;
            if (kb != null)
            {
                if (kb.aKey.isPressed || kb.leftArrowKey.isPressed)  h -= 1f;
                if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) h += 1f;
                if (kb.wKey.isPressed || kb.upArrowKey.isPressed)    v += 1f;
                if (kb.sKey.isPressed || kb.downArrowKey.isPressed)  v -= 1f;
            }

            // CAMERA-RELATIVE movement: W goes the way the camera (mouse) is looking,
            // not world +Z. Basis is the active game camera flattened onto the ground.
            Vector3 camF = Vector3.forward, camR = Vector3.right;
            var cam = Camera.main;
            if (cam != null)
            {
                camF = Vector3.ProjectOnPlane(cam.transform.forward, Vector3.up);
                if (camF.sqrMagnitude < 1e-4f) camF = Vector3.ProjectOnPlane(cam.transform.up, Vector3.up); // looking straight down
                camF.Normalize();
                camR = Vector3.ProjectOnPlane(cam.transform.right, Vector3.up).normalized;
            }

            Vector3 input = camR * h + camF * v;
            if (input.sqrMagnitude > 1f) input.Normalize();
            Vector3 desired = input * moveSpeed;

            // Ramp velocity toward the target instead of snapping (smooth start/stop).
            float rate = input.sqrMagnitude > 0.001f ? acceleration : deceleration;
            _horizVel = Vector3.MoveTowards(_horizVel, desired, rate * Time.deltaTime);

            if (_cc.isGrounded && _vy < 0f) _vy = -2f;
            _vy += gravity * Time.deltaTime;

            _cc.Move((_horizVel + new Vector3(0f, _vy, 0f)) * Time.deltaTime);

            if (_horizVel.sqrMagnitude > 0.05f)
            {
                Quaternion target = Quaternion.LookRotation(new Vector3(_horizVel.x, 0f, _horizVel.z));
                transform.rotation = Quaternion.RotateTowards(transform.rotation, target, turnSpeed * Time.deltaTime);
            }
        }
    }
}
