using System;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages.Transfer;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Utility;

namespace Robust.Server.Network.Transfer;

internal sealed class ServerTransferImplWebSocket : TransferImplWebSocket
{
    private readonly IConfigurationManager _cfg;
    private readonly INetManager _netManager;
    private readonly SemaphoreSlim _apiLock = new(1, 1);
    private readonly TaskCompletionSource _connectTcs = new();

    // To authenticate the client doing the HTTP request,
    // we ask that they provide a key we gave them via Lidgren traffic.
    public byte[]? Key;

    public ServerTransferImplWebSocket(
        ISawmill sawmill,
        BaseTransferManager parent,
        IConfigurationManager cfg,
        INetManager netManager,
        INetChannel channel)
        : base(sawmill, parent, channel)
    {
        _cfg = cfg;
        _netManager = netManager;
    }

    public override Task ServerInit()
    {
        Key = RandomNumberGenerator.GetBytes(RandomKeyBytes);

        var uriBuilder = new UriBuilder(string.Concat(
            _cfg.GetCVar(CVars.TransferHttpEndpoint).TrimEnd("/"),
            ServerTransferManager.TransferApiUrl));

        uriBuilder.Scheme = uriBuilder.Scheme switch
        {
            "http" => "ws",
            "https" => "wss",
            _ => throw new InvalidOperationException($"Invalid API endpoint scheme: {uriBuilder.Scheme}")
        };

        var url = uriBuilder.ToString();

        Sawmill.Verbose($"Transfer API URL is '{url}'");

        var initMsg = new MsgTransferInit();
        initMsg.HttpInfo = (url, Key);

        _netManager.ServerSendMessage(initMsg, Channel);

        return _connectTcs.Task;
    }

    public override Task ClientInit(CancellationToken cancel)
    {
        throw new NotSupportedException();
    }

    public async Task HandleApiRequest(NetUserId userId, IStatusHandlerContext context)
    {
        using var _ = await _apiLock.WaitGuardAsync();

        if (Key == null)
        {
            Sawmill.Warning($"HTTP request failed: UserID '{userId}' tried to connect twice");
            await context.RespondErrorAsync(HttpStatusCode.BadRequest);
            return;
        }

        if (!context.RequestHeaders.TryGetValue(KeyHeaderName, out var keyValue) || keyValue is not [{ } keyValueStr])
        {
            await context.RespondErrorAsync(HttpStatusCode.BadRequest);
            return;
        }

        var buf = new byte[RandomKeyBytes];

        if (!Convert.TryFromBase64String(keyValueStr, buf, out var written) || written != RandomKeyBytes)
        {
            Sawmill.Verbose("HTTP request failed: key is not valid base64 or wrong length");
            await context.RespondErrorAsync(HttpStatusCode.BadRequest);
            return;
        }

        if (!CryptographicOperations.FixedTimeEquals(buf, Key))
        {
            Sawmill.Warning("HTTP request failed: key is wrong");
            await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
            return;
        }

        Sawmill.Debug("Client connect to transfer WS channel: {UserId}", userId);

        WebSocket = await context.AcceptWebSocketAsync();

        // We've connected.
        // Clear key so this can't be reconnected to.
        Key = null;

        _connectTcs.TrySetResult();

        ReadThread();
    }

    public override void Dispose()
    {
        _connectTcs.TrySetCanceled();

        base.Dispose();
    }
}
