using Unity.Netcode;
using UnityEngine;

namespace Jankenbots.Prototype
{
    /// <summary>
    /// SeatManager — decides WHICH player drives WHICH tread of the shared bot.
    ///
    /// M1 coordination feel: two humans each pilot half of one janky body, so the
    /// whole point is that a seat is a scarce, claimable slot. This lives on the
    /// bot chassis GameObject (alongside the chassis Rigidbody / NetworkObject) and
    /// is the single source of truth for seat ownership.
    ///
    /// Model:
    ///   - Two seats, Left and Right.
    ///   - Each seat stores the ulong clientId of its pilot, or <see cref="NoPilot"/>
    ///     (== NEUTRAL / free) when nobody holds it.
    ///   - First player to ask claims Left, the next claims Right, everyone after
    ///     that gets nothing (a spectator for M1). Treads with no pilot stay NEUTRAL
    ///     and simply apply no force — the bot just doesn't move on that side.
    ///
    /// Authority: server-only writes. Seat state is host-authoritative just like the
    /// physics, so clients never guess — they read replicated NetworkVariables and
    /// send a ServerRpc to *ask* for a seat.
    ///
    /// TreadPart hooks into this via <see cref="OwnsSeat"/>: a TreadPart is tagged
    /// Left or Right, and on the host it only honors input from the client that
    /// currently owns that seat.
    /// </summary>
    [RequireComponent(typeof(NetworkObject))]
    public class SeatManager : NetworkBehaviour
    {
        public enum Seat { Left, Right }

        // Sentinel for "this seat is empty / NEUTRAL". Real clientIds are small
        // ulongs starting at 0, so MaxValue is a safe "nobody" marker. We can't use
        // -1 because clientId is unsigned.
        public const ulong NoPilot = ulong.MaxValue;

        // Replicated seat ownership. Server writes, everyone reads. Clients subscribe
        // to OnValueChanged if they want to react (e.g. update the seat-claim UI);
        // for M1 we just poll them in OnGUI, which is plenty.
        public readonly NetworkVariable<ulong> LeftPilot =
            new NetworkVariable<ulong>(NoPilot,
                NetworkVariableReadPermission.Everyone,
                NetworkVariableWritePermission.Server);

        public readonly NetworkVariable<ulong> RightPilot =
            new NetworkVariable<ulong>(NoPilot,
                NetworkVariableReadPermission.Everyone,
                NetworkVariableWritePermission.Server);

        // ---------------------------------------------------------------------
        // Leaver-safety wiring
        // ---------------------------------------------------------------------
        // If a pilot rage-quits (or their WiFi dies), their seat MUST free up so a
        // remaining/new player can grab it — otherwise the bot is permanently
        // half-dead. We hook client-disconnect on the server and clear any seat that
        // pointed at the departing client.

        public override void OnNetworkSpawn()
        {
            // Only the server owns seat state, so only the server needs the hook.
            if (IsServer)
            {
                NetworkManager.Singleton.OnClientDisconnectCallback += HandleClientDisconnect;
            }
        }

        public override void OnNetworkDespawn()
        {
            if (IsServer && NetworkManager.Singleton != null)
            {
                NetworkManager.Singleton.OnClientDisconnectCallback -= HandleClientDisconnect;
            }
        }

        // Leaver-safety: free whichever seat the departing pilot held.
        void HandleClientDisconnect(ulong clientId)
        {
            if (LeftPilot.Value == clientId) LeftPilot.Value = NoPilot;
            if (RightPilot.Value == clientId) RightPilot.Value = NoPilot;
        }

        // ---------------------------------------------------------------------
        // Claiming a seat
        // ---------------------------------------------------------------------
        // A client presses "join the bot"; we don't let it pick a side — first come
        // gets Left, second gets Right. This keeps M1 dead simple and mirrors the
        // "first two players claim left/right tread" scope.

        /// <summary>
        /// Client asks the host for a seat. RequireOwnership is false because the
        /// asking client does NOT own the bot (the host does) — any client may ask.
        /// The sender's clientId comes from RpcParams, so a client can't lie about
        /// who it is.
        /// </summary>
        [Rpc(SendTo.Server, RequireOwnership = false)]
        public void ClaimSeatRpc(RpcParams rpcParams = default)
        {
            ulong requester = rpcParams.Receive.SenderClientId;

            // Already seated? Do nothing (idempotent — safe to spam the button).
            if (LeftPilot.Value == requester || RightPilot.Value == requester)
                return;

            // Fill Left first, then Right. Anyone after that is a spectator for M1.
            if (LeftPilot.Value == NoPilot)
                LeftPilot.Value = requester;
            else if (RightPilot.Value == NoPilot)
                RightPilot.Value = requester;
            // else: bot is full — silently ignore.
        }

        /// <summary>
        /// Voluntarily give up your seat (frees it as NEUTRAL). Handy for testing and
        /// for letting a player hop from Left to Right. Server-authoritative.
        /// </summary>
        [Rpc(SendTo.Server, RequireOwnership = false)]
        public void ReleaseSeatRpc(RpcParams rpcParams = default)
        {
            ulong requester = rpcParams.Receive.SenderClientId;
            if (LeftPilot.Value == requester) LeftPilot.Value = NoPilot;
            if (RightPilot.Value == requester) RightPilot.Value = NoPilot;
        }

        // ---------------------------------------------------------------------
        // Queries used by TreadPart (host-side input gating)
        // ---------------------------------------------------------------------

        /// <summary>Does <paramref name="clientId"/> currently pilot <paramref name="seat"/>?
        /// TreadPart calls this on the host to decide whether to honor an input RPC.</summary>
        public bool OwnsSeat(Seat seat, ulong clientId)
        {
            return PilotOf(seat) == clientId && clientId != NoPilot;
        }

        /// <summary>ClientId piloting a seat, or <see cref="NoPilot"/> if free.</summary>
        public ulong PilotOf(Seat seat)
        {
            return seat == Seat.Left ? LeftPilot.Value : RightPilot.Value;
        }

        /// <summary>Which seat (if any) the local player holds — for local UI only.</summary>
        Seat? LocalSeat()
        {
            if (NetworkManager.Singleton == null) return null;
            ulong me = NetworkManager.Singleton.LocalClientId;
            if (LeftPilot.Value == me) return Seat.Left;
            if (RightPilot.Value == me) return Seat.Right;
            return null;
        }

        // ---------------------------------------------------------------------
        // Throwaway debug UI — good enough for playtesting with friends.
        // ---------------------------------------------------------------------
        void OnGUI()
        {
            if (!IsSpawned) return;

            Seat? mine = LocalSeat();
            string held = mine.HasValue ? mine.Value.ToString() : "none (spectator)";

            // Sits BELOW the NetworkBootstrap connectivity panel (which occupies the
            // top-left ~260px) so the Claim button isn't hidden behind it.
            const int x = 10, y = 300, w = 260, h = 78;
            GUI.Box(new Rect(x, y, w, h), "JANKENBOTS · seat");
            GUI.Label(new Rect(x + 10, y + 22, w - 20, 20), $"You hold: {held}");
            GUI.Label(new Rect(x + 10, y + 42, w - 20, 20),
                $"Left: {SeatLabel(LeftPilot.Value)}   Right: {SeatLabel(RightPilot.Value)}");

            // Client-side ask/release buttons (they just fire the ServerRpc).
            if (!mine.HasValue)
            {
                if (GUI.Button(new Rect(x + w - 70, y + 20, 55, 20), "Claim"))
                    ClaimSeatRpc();
            }
            else if (GUI.Button(new Rect(x + w - 70, y + 20, 55, 20), "Leave"))
            {
                ReleaseSeatRpc();
            }
        }

        static string SeatLabel(ulong pilot) => pilot == NoPilot ? "free" : $"#{pilot}";
    }
}
