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

namespace Jankenbots.Prototype
{
    /// <summary>
    /// JANKENBOTS M1 — ONE tread of the shared janky bot.
    ///
    /// This is the HEART of the control-feel test. Everything here exists to answer
    /// one question: is it FUN for two friends to each drive one tread of the same
    /// clumsy body and try to make it go where they want together?
    ///
    /// ARCHITECTURE (host-authoritative, no prediction — see the M1 cheat-sheet):
    ///   * There is ONE simulated Rigidbody + ONE NetworkObject: the CHASSIS. Both
    ///     treads are plain CHILD GameObjects (no Rigidbody, no NetworkObject of their
    ///     own); each TreadPart is a NetworkBehaviour bound to the chassis' shared
    ///     NetworkObject. (NGO forbids nested NetworkObjects in a spawned prefab, so
    ///     per-tread ownership is impossible — we gate on the SEAT instead.)
    ///   * Differential drive "emerges" because the two treads apply their forces at
    ///     different WORLD POSITIONS (left vs right of centre).
    ///   * SEAT-BASED input gating (via <see cref="SeatManager"/>): a client only reads
    ///     &amp; sends input for a tread whose seat it currently holds. The host re-checks
    ///     seat ownership before honoring any input RPC, so a client can't drive a tread
    ///     it doesn't pilot.
    ///   * The host (IsServer) caches the latest validated input per tread and applies
    ///     ALL forces in FixedUpdate. NGO's NetworkRigidbody/NetworkTransform on the
    ///     chassis replicates the resulting motion back to everyone.
    ///
    /// FEEL MODEL v0.1 — three deliberately "janky" verbs, each a literal force:
    ///   (a) SNAP-TO-CRUISE THROTTLE  — slam forward → a fixed CRUISE force; partial is
    ///       proportional but the intent is over-commitment (that's the comedy).
    ///   (b) LOCK-PIVOT  — hold a button and THIS tread plants as an anchor; the bot
    ///       swings about it like a pinned foot.
    ///   (c) PASSIVE LEAN  — sideways stick dumps a ballast torque to counter-roll the
    ///       top-heavy bot; a constant shared balancing chore.
    ///
    /// Tuning fields are public so we can dial the feel live in the inspector while
    /// friends are playing. Numbers here are only sane starting points.
    /// </summary>
    [DisallowMultipleComponent]
    public class TreadPart : NetworkBehaviour
    {
        // Which side of the bot this tread is. Drives the SEAT it maps to (Left/Right)
        // AND, because the tread physically SITS left/right of centre, the differential
        // drive. Set per-tread in the inspector.
        public enum Side { Left, Right }

        [Header("Identity")]
        [Tooltip("Which tread this is → which SeatManager seat it maps to, and (via its world position) which side of the chassis it pushes.")]
        public Side side = Side.Left;

        [Header("Shared body")]
        [Tooltip("The ONE simulated chassis Rigidbody every tread pushes on. Leave empty to auto-find on a parent.")]
        public Rigidbody chassis;

        // ---- (a) SNAP-TO-CRUISE THROTTLE tuning ----------------------------------
        [Header("(a) Throttle — snap-to-cruise")]
        [Tooltip("Force (Newtons) applied at full-forward stick. This is the 'cruise' the tread snaps to.")]
        public float cruiseForce = 1200f;

        [Tooltip("Below this stick magnitude we treat throttle as zero.")]
        [Range(0f, 0.5f)]
        public float throttleDeadzone = 0.08f;

        // ---- (b) LOCK-PIVOT tuning -----------------------------------------------
        [Header("(b) Lock-pivot — plant this tread as an anchor")]
        [Tooltip("How hard the planted tread resists the chassis sliding at its position.")]
        public float pivotAnchorStrength = 2500f;

        [Tooltip("Extra angular damping (torque opposing spin) while planting.")]
        public float pivotAngularResistance = 400f;

        // ---- (c) PASSIVE LEAN tuning ---------------------------------------------
        [Header("(c) Passive lean — anti-tip ballast")]
        [Tooltip("Ballast torque (N·m) at full sideways stick, applied along the drive (forward) axis.")]
        public float leanTorque = 800f;

        [Tooltip("Below this sideways magnitude, no lean torque.")]
        [Range(0f, 0.5f)]
        public float leanDeadzone = 0.08f;

        // ---- Debug / testing -----------------------------------------------------
        [Header("Debug / testing")]
        [Tooltip("HOST ONLY: when true, this tread ignores seat/input and drives at full cruise every FixedUpdate. Lets ONE machine feel two-tread coordination — the host auto-drives one tread while a friend/clone drives the other. Leave OFF for real play.")]
        public bool debugAutoDriveFull = false;

        // The seat this tread maps to, resolved once from `side`.
        SeatManager.Seat MySeat => side == Side.Left ? SeatManager.Seat.Left : SeatManager.Seat.Right;

        // Source of truth for who pilots which tread. Found on a parent (the chassis).
        SeatManager _seats;

        // --------------------------------------------------------------------------
        // HOST-SIDE cached input. Written ONLY by the (validated) RPC on the server and
        // read ONLY in FixedUpdate (also server-gated). No sync primitive needed — the
        // authoritative simulation is entirely host-local.
        // --------------------------------------------------------------------------
        float _throttle;   // 0..1
        bool  _pivotHeld;
        float _lean;       // -1..1

        void Awake()
        {
            if (chassis == null)
                chassis = GetComponentInParent<Rigidbody>();
            _seats = GetComponentInParent<SeatManager>();
        }

        // ==========================================================================
        // CLIENT: if the LOCAL player holds this tread's seat, read input and ship it
        // to the host. Nothing is applied locally. Seat ownership — not NetworkObject
        // ownership — is the gate, so each client self-selects the tread it pilots.
        // ==========================================================================
        void Update()
        {
            if (!IsSpawned || _seats == null) return;

            ulong me = NetworkManager.Singleton.LocalClientId;
            if (_seats.PilotOf(MySeat) != me) return;   // I don't drive this tread

            float throttle = ReadThrottle();   // 0..1
            bool  pivot    = ReadPivotHeld();
            float lean     = ReadLean();        // -1..1

            SubmitTreadInputRpc(throttle, pivot, lean);
        }

        /// <summary>
        /// Owning pilot's client → host. RequireOwnership is FALSE (the bot's
        /// NetworkObject is owned by the server, not the pilot) — instead the host
        /// re-validates the SEAT so a client can only drive the tread it actually
        /// pilots. The host just caches; forces are applied in FixedUpdate.
        /// </summary>
        [Rpc(SendTo.Server, RequireOwnership = false)]
        void SubmitTreadInputRpc(float throttle, bool pivotHeld, float lean, RpcParams rpcParams = default)
        {
            // Anti-spoof: only honor input from the client that currently holds this seat.
            if (_seats == null || !_seats.OwnsSeat(MySeat, rpcParams.Receive.SenderClientId))
                return;

            _throttle  = Mathf.Clamp01(throttle);
            _pivotHeld = pivotHeld;
            _lean      = Mathf.Clamp(lean, -1f, 1f);
        }

        // ==========================================================================
        // HOST ONLY: turn the cached input into literal forces on the SHARED chassis.
        // Two TreadParts running this in the same FixedUpdate, pushing at their two
        // different world positions, ARE the differential drive.
        // ==========================================================================
        void FixedUpdate()
        {
            if (!IsServer) return;      // authority guard — clients never simulate
            if (chassis == null) return;

            // Test affordance: drive full-forward regardless of seat/input, so one
            // machine can feel two-tread coordination (host drives this tread, a
            // clone/friend drives the other). Off for real play.
            if (debugAutoDriveFull)
            {
                chassis.AddForceAtPosition(transform.forward * cruiseForce, transform.position, ForceMode.Force);
                return;
            }

            // Un-piloted tread goes limp: no pilot → no cached force. (Also clears any
            // stale input the instant a pilot leaves, so the bot doesn't coast on it.)
            if (_seats == null || _seats.PilotOf(MySeat) == SeatManager.NoPilot)
            {
                _throttle = 0f; _pivotHeld = false; _lean = 0f;
                return;
            }

            Vector3 treadPos = transform.position;   // where THIS tread pushes from

            if (_pivotHeld)
            {
                // ---- (b) LOCK-PIVOT --------------------------------------------
                // Plant this tread: cancel linear slip at its position and bleed spin
                // so the OTHER tread's thrust swings the bot about this point.
                Vector3 pointVel = chassis.GetPointVelocity(treadPos);
                chassis.AddForceAtPosition(-pointVel * pivotAnchorStrength, treadPos, ForceMode.Force);
                chassis.AddTorque(-chassis.angularVelocity * pivotAngularResistance, ForceMode.Force);
                return; // a planted tread is an anchor, not a motor
            }

            // ---- (a) SNAP-TO-CRUISE THROTTLE -----------------------------------
            // Full stick == full cruiseForce; partial scales down. Applied AT the
            // tread's world position along its own forward → left/right offset =
            // differential drive (asymmetric throttle turns the bot).
            if (_throttle > throttleDeadzone)
            {
                Vector3 drive = transform.forward * (_throttle * cruiseForce);
                chassis.AddForceAtPosition(drive, treadPos, ForceMode.Force);
            }

            // ---- (c) PASSIVE LEAN — anti-tip ballast ---------------------------
            if (Mathf.Abs(_lean) > leanDeadzone)
            {
                chassis.AddTorque(transform.forward * (_lean * leanTorque), ForceMode.Force);
            }
        }

        // ==========================================================================
        // INPUT READERS — new Input System (project is Input-System-New-only, so legacy
        // UnityEngine.Input would throw). Same keys for both seats; the SEAT gate in
        // Update() decides which tread a given client actually drives. Only called for
        // the tread the local player pilots. Swap for per-seat gamepad actions later.
        //   Throttle = W / Up   ·   Pivot = Left Shift   ·   Lean = A/D or Left/Right
        // ==========================================================================

        /// <summary>0..1 throttle. Forward only (no reverse in v0.1).</summary>
        float ReadThrottle()
        {
            var kb = Keyboard.current;
            if (kb == null) return 0f;
            float v = (kb.wKey.isPressed || kb.upArrowKey.isPressed) ? 1f : 0f;
            return v < throttleDeadzone ? 0f : v;
        }

        /// <summary>Is the plant-pivot button held?</summary>
        bool ReadPivotHeld()
        {
            var kb = Keyboard.current;
            return kb != null && (kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed);
        }

        /// <summary>-1..1 sideways ballast request.</summary>
        float ReadLean()
        {
            var kb = Keyboard.current;
            if (kb == null) return 0f;
            float h = 0f;
            if (kb.aKey.isPressed || kb.leftArrowKey.isPressed)  h -= 1f;
            if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) h += 1f;
            return Mathf.Abs(h) < leanDeadzone ? 0f : h;
        }
    }
}
