using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Options;
using Prospect.Server.Api.Config;
using Prospect.Server.Api.Services.Auth.Extensions;
using Prospect.Server.Api.Services.CloudScript.Models;

namespace Prospect.Server.Api.Services.CloudScript.Functions;

[CloudScriptFunction("GetSignalRConnection")]
public class GetSignalRConnection : ICloudScriptFunction<FYGetSignalRConnection, FYGetSignalRConnectionResult>
{
    // LAN address of the homelab host. Clients on the local network get the SignalR URL on
    // this IP (which their cert already validates via the IP/DNS SAN), while external clients
    // keep the public domain from configuration. This matters because the real-time WebSocket
    // (libwebsockets, incl. under Proton/Wine) can't reach the public domain from inside the
    // LAN (hairpin NAT), which surfaced as "error code 5" right after the intro.
    private const string LanHost = "192.168.1.136";

    private readonly PlayFabSettings _settings;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<GetSignalRConnection> _logger;

    public GetSignalRConnection(IOptions<PlayFabSettings> settings, IHttpContextAccessor httpContextAccessor, ILogger<GetSignalRConnection> logger)
    {
        _settings = settings.Value;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public Task<FYGetSignalRConnectionResult> ExecuteAsync(FYGetSignalRConnection request)
    {
        // The game client connects to SignalR only over HTTPS.
        var url = _settings.SignalRURL;

        var remote = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
        var lan = remote != null && IsLan(remote);
        if (lan)
        {
            try
            {
                url = new UriBuilder(url) { Host = LanHost }.Uri.AbsoluteUri;
            }
            catch { /* keep the configured URL on any parsing issue */ }
        }

        // Tag the connection with the player id so the hub can map connection -> user and
        // target server messages (deploy signal) instead of broadcasting to everyone.
        var userId = _httpContextAccessor.HttpContext?.User.FindAuthUserId();
        if (!string.IsNullOrEmpty(userId))
            url += (url.Contains('?') ? "&" : "?") + "uid=" + Uri.EscapeDataString(userId);

        _logger.LogInformation("GetSignalRConnection: remoteIp={Remote} lan={Lan} uid={Uid} -> {Url}", remote, lan, userId, url);

        return Task.FromResult(new FYGetSignalRConnectionResult
        {
            Url = url,
            AccessToken = _settings.SignalRAccessToken
        });
    }

    private static bool IsLan(IPAddress ip)
    {
        if (IPAddress.IsLoopback(ip)) return true;
        if (ip.IsIPv4MappedToIPv6) ip = ip.MapToIPv4();
        if (ip.AddressFamily != AddressFamily.InterNetwork) return false;
        var b = ip.GetAddressBytes();
        return b[0] == 10
            || (b[0] == 172 && b[1] >= 16 && b[1] <= 31)
            || (b[0] == 192 && b[1] == 168);
    }
}
