// JANKENBOTS — M1 step-1 connectivity harness.
//
// This is THROWAWAY prototype code. Its only job is to get two friends into the
// same networked session with zero port-forwarding: one clicks Host (which spins
// up a Unity Relay allocation and prints a short join code), the other types that
// code and clicks Join. Everything is host-authoritative from here on out; this
// file just establishes the pipe.
//
// Attach this component to the SAME GameObject that carries NetworkManager +
// UnityTransport (see the M1 scene-wiring notes). The IMGUI panel at the bottom is
// deliberately ugly — it exists so we can smoke-test connectivity before any real
// UI exists.

using System;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using Unity.Services.Core;
using Unity.Services.Authentication;
using Unity.Services.Relay;          // RelayService, RelayServiceException
using Unity.Services.Relay.Models;   // Allocation, JoinAllocation, AllocationUtils  <-- REQUIRED for AllocationUtils

public class NetworkBootstrap : MonoBehaviour
{
    [Header("Relay")]
    // maxConnections = number of CLIENTS excluding the host. Keep this small for M1
    // smoke-testing; bump toward 24 for the ~25-player target later.
    [SerializeField] private int maxConnections = 4;

    // "dtls" = encrypted UDP, the right choice for desktop friends. ("udp" is
    // unencrypted; "wss" is WebGL-only and would also need utp.UseWebSockets = true.)
    [SerializeField] private string connectionType = "dtls";

    // Cached transport pulled off the same GameObject at startup.
    private UnityTransport _utp;

    // The join code the host hands out. Empty until we've successfully hosted.
    private string _hostJoinCode = "";

    // What the joining player types in. Bound to the OnGUI text field.
    private string _joinCodeInput = "";

    // Simple UI/log line so the harness gives feedback without opening the console.
    private string _status = "Idle — Host or Join to begin.";

    // Guard so double-clicking Host/Join doesn't fire two overlapping async flows.
    private bool _busy;

    // -----------------------------------------------------------------------------
    // Startup: bring Unity Services online and sign in anonymously ONCE. Relay and
    // Auth both refuse to work until this completes, so we do it eagerly on Start
    // rather than lazily inside Host/Join (which also re-guard, so it's safe either way).
    // -----------------------------------------------------------------------------
    private async void Start()
    {
        _utp = NetworkManager.Singleton != null
            ? NetworkManager.Singleton.GetComponent<UnityTransport>()
            : GetComponent<UnityTransport>();

        if (_utp == null)
            Debug.LogError("[NetworkBootstrap] No UnityTransport found on the NetworkManager GameObject.");

        await EnsureServicesReady();
    }

    // Idempotent: safe to call before every Host/Join. Guarded by the Services state
    // machine and the sign-in flag so we never double-initialize or double-sign-in.
    private async Task EnsureServicesReady()
    {
        try
        {
            if (UnityServices.State != ServicesInitializationState.Initialized)
                await UnityServices.InitializeAsync();

            if (!AuthenticationService.Instance.IsSignedIn)
                await AuthenticationService.Instance.SignInAnonymouslyAsync();
        }
        catch (Exception e)
        {
            // A linked Unity Cloud project ID (Project Settings ▸ Services) is required
            // or InitializeAsync throws here.
            Debug.LogError($"[NetworkBootstrap] Services init/sign-in failed: {e}");
            _status = "Services init failed — see console.";
            throw; // let the caller abort its Host/Join flow.
        }
    }

    // -----------------------------------------------------------------------------
    // HOST: create a Relay allocation, feed it into UnityTransport, fetch the human-
    // friendly join code, then StartHost(). Returns the join code (or null on failure).
    // -----------------------------------------------------------------------------
    public async Task<string> HostGame()
    {
        if (_busy) return null;
        _busy = true;
        _status = "Hosting…";
        try
        {
            await EnsureServicesReady();

            // Reserve a slot on Relay. This is where the actual relay server is picked.
            Allocation allocation = await RelayService.Instance.CreateAllocationAsync(maxConnections);

            // Hand the allocation to the transport. NOTE: with the unified
            // com.unity.services.multiplayer SDK, AllocationUtils.ToRelayServerData is the
            // ONLY valid conversion — the old `new RelayServerData(allocation, "dtls")`
            // ctor does not exist here.
            _utp.SetRelayServerData(AllocationUtils.ToRelayServerData(allocation, connectionType));

            // The short code friends will type to join.
            _hostJoinCode = await RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);

            // SetRelayServerData must happen BEFORE StartHost. StartHost() returns bool.
            if (!NetworkManager.Singleton.StartHost())
            {
                Debug.LogError("[NetworkBootstrap] StartHost() returned false.");
                _status = "StartHost failed — see console.";
                return null;
            }

            _status = $"Hosting. Join code: {_hostJoinCode}";
            Debug.Log($"[NetworkBootstrap] Hosting with join code {_hostJoinCode}");
            return _hostJoinCode;
        }
        catch (RelayServiceException e)
        {
            Debug.LogError($"[NetworkBootstrap] Relay error while hosting: {e}");
            _status = "Relay error while hosting — see console.";
            return null;
        }
        catch (Exception e)
        {
            Debug.LogError($"[NetworkBootstrap] Unexpected error while hosting: {e}");
            _status = "Host failed — see console.";
            return null;
        }
        finally
        {
            _busy = false;
        }
    }

    // -----------------------------------------------------------------------------
    // JOIN: resolve the join code into a JoinAllocation, configure transport, StartClient().
    // -----------------------------------------------------------------------------
    public async Task<bool> JoinGame(string joinCode)
    {
        if (_busy) return false;
        if (string.IsNullOrWhiteSpace(joinCode))
        {
            Debug.LogError("[NetworkBootstrap] JoinGame called with an empty code.");
            _status = "Enter a join code first.";
            return false;
        }

        _busy = true;
        _status = "Joining…";
        try
        {
            await EnsureServicesReady();

            // Relay codes are case-insensitive but we trim stray whitespace/newlines.
            JoinAllocation allocation =
                await RelayService.Instance.JoinAllocationAsync(joinCode.Trim());

            _utp.SetRelayServerData(AllocationUtils.ToRelayServerData(allocation, connectionType));

            if (!NetworkManager.Singleton.StartClient())
            {
                Debug.LogError("[NetworkBootstrap] StartClient() returned false.");
                _status = "StartClient failed — see console.";
                return false;
            }

            _status = "Joined. Waiting for host…";
            Debug.Log("[NetworkBootstrap] Client started, connecting to host via Relay.");
            return true;
        }
        catch (RelayServiceException e)
        {
            Debug.LogError($"[NetworkBootstrap] Relay error while joining: {e}");
            _status = "Bad code / Relay error — see console.";
            return false;
        }
        catch (Exception e)
        {
            Debug.LogError($"[NetworkBootstrap] Unexpected error while joining: {e}");
            _status = "Join failed — see console.";
            return false;
        }
        finally
        {
            _busy = false;
        }
    }

    // -----------------------------------------------------------------------------
    // Dead-simple IMGUI harness. Once we're connected the buttons hide themselves and
    // we just show status + (for the host) a selectable join code you can copy out.
    // async void event handlers are fine for a throwaway prototype.
    // -----------------------------------------------------------------------------
    private void OnGUI()
    {
        const float pad = 10f;
        GUILayout.BeginArea(new Rect(pad, pad, 320f, 260f), GUI.skin.box);

        GUILayout.Label("JANKENBOTS — M1 connectivity");
        GUILayout.Label(_status);
        GUILayout.Space(6f);

        bool live = NetworkManager.Singleton != null &&
                    (NetworkManager.Singleton.IsHost ||
                     NetworkManager.Singleton.IsServer ||
                     NetworkManager.Singleton.IsClient);

        if (!live)
        {
            GUI.enabled = !_busy;

            // HOST -------------------------------------------------------------
            if (GUILayout.Button("Host", GUILayout.Height(30f)))
                _ = HostGame(); // fire-and-forget; HostGame owns its own error logging.

            GUILayout.Space(8f);

            // JOIN -------------------------------------------------------------
            GUILayout.Label("Join code:");
            _joinCodeInput = GUILayout.TextField(_joinCodeInput, GUILayout.Height(24f));
            if (GUILayout.Button("Join", GUILayout.Height(30f)))
                _ = JoinGame(_joinCodeInput);

            GUI.enabled = true;
        }
        else
        {
            // Connected. If we're the host, surface the code as a selectable text field
            // so the tester can highlight + copy it to paste into chat.
            if (NetworkManager.Singleton.IsHost && !string.IsNullOrEmpty(_hostJoinCode))
            {
                GUILayout.Label("Share this join code:");
                // A read-only-ish selectable field: users can select/copy, edits are ignored.
                GUILayout.TextField(_hostJoinCode, GUILayout.Height(28f));
            }

            GUILayout.Space(8f);
            if (GUILayout.Button("Disconnect", GUILayout.Height(28f)))
            {
                NetworkManager.Singleton.Shutdown();
                _hostJoinCode = "";
                _status = "Disconnected.";
            }
        }

        GUILayout.EndArea();
    }
}
