namespace NATS.Server.WebSocket;

/// <summary>
/// Validates websocket options against server-wide auth/TLS/operator settings.
/// Go reference: websocket.go validateWebsocketOptions.
/// </summary>
public static class WebSocketOptionsValidator
{
    private static readonly HashSet<string> ReservedResponseHeaders = new(StringComparer.OrdinalIgnoreCase)
    {
        "Upgrade",
        "Connection",
        "Sec-WebSocket-Accept",
        "Sec-WebSocket-Extensions",
        "Sec-WebSocket-Protocol",
    };

    public static WebSocketOptionsValidationResult Validate(NatsOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);

        var ws = options.WebSocket;
        var errors = new List<string>();

        if (ws.Port < 0)
            return new WebSocketOptionsValidationResult(true, errors);

        if (!ws.NoTls)
        {
            var hasCert = !string.IsNullOrWhiteSpace(ws.TlsCert);
            var hasKey = !string.IsNullOrWhiteSpace(ws.TlsKey);
            if (!hasCert || !hasKey)
                errors.Add("WebSocket TLS listener requires both TlsCert and TlsKey when NoTls is false.");
        }

        if (ws.AllowedOrigins is { Count: > 0 })
        {
            foreach (var origin in ws.AllowedOrigins)
            {
                if (!Uri.TryCreate(origin, UriKind.Absolute, out var uri)
                    || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
                {
                    errors.Add($"Invalid websocket allowed origin: '{origin}'.");
                }
            }
        }

        if (!string.IsNullOrWhiteSpace(ws.NoAuthUser) && options.Users is { Count: > 0 })
        {
            var match = options.Users.Any(u => string.Equals(u.Username, ws.NoAuthUser, StringComparison.Ordinal));
            if (!match)
                errors.Add("WebSocket NoAuthUser must match one of the configured users.");
        }

        if ((!string.IsNullOrWhiteSpace(ws.Username) || !string.IsNullOrWhiteSpace(ws.Token))
            && ((options.Users?.Count ?? 0) > 0 || (options.NKeys?.Count ?? 0) > 0))
        {
            errors.Add("WebSocket Username/Token cannot be set when users or nkeys are configured.");
        }

        if (!string.IsNullOrWhiteSpace(ws.JwtCookie) && (options.TrustedKeys == null || options.TrustedKeys.Length == 0))
        {
            errors.Add("WebSocket JwtCookie requires trusted operators (TrustedKeys).");
        }

        if (options.TlsPinnedCerts is { Count: > 0 })
        {
            if (ws.NoTls)
                errors.Add("WebSocket TLSPinnedCerts require TLS (NoTls must be false).");

            foreach (var pin in options.TlsPinnedCerts)
            {
                if (string.IsNullOrWhiteSpace(pin) || !pin.All(Uri.IsHexDigit))
                    errors.Add($"Invalid websocket pinned cert hash: '{pin}'.");
            }
        }

        if (ws.Headers is { Count: > 0 })
        {
            foreach (var headerName in ws.Headers.Keys)
            {
                if (ReservedResponseHeaders.Contains(headerName))
                    errors.Add($"WebSocket header '{headerName}' is reserved and cannot be overridden.");
            }
        }

        return new WebSocketOptionsValidationResult(errors.Count == 0, errors);
    }
}

public sealed record WebSocketOptionsValidationResult(
    bool IsValid,
    IReadOnlyList<string> Errors);
