using System.Net.Http;

namespace ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;

/// <summary>
/// OCSP responder communication: fetches raw OCSP response bytes from CA endpoints.
/// Mirrors certidp/ocsp_responder.go.
/// </summary>
public static class OcspResponder
{
    /// <summary>
    /// Fetches an OCSP response from the responder URLs in <paramref name="link"/>.
    /// Tries each endpoint in order and returns the first successful response.
    /// </summary>
    /// <param name="link">Chain link containing leaf cert, issuer cert, and OCSP endpoints.</param>
    /// <param name="opts">Configuration (timeout, etc.).</param>
    /// <param name="log">Optional logger.</param>
    /// <param name="ocspRequest">DER-encoded OCSP request bytes to send.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>Raw DER bytes of the OCSP response.</returns>
    public static async Task<byte[]> FetchOCSPResponseAsync(
        ChainLink link,
        OcspPeerConfig opts,
        byte[] ocspRequest,
        OcspLog? log = null,
        CancellationToken cancellationToken = default)
    {
        if (link.Leaf is null || link.Issuer is null)
            throw new ArgumentException(OcspMessages.ErrInvalidChainlink, nameof(link));
        if (link.OcspWebEndpoints is null || link.OcspWebEndpoints.Count == 0)
            throw new InvalidOperationException(OcspMessages.ErrNoAvailOCSPServers);

        var timeout = TimeSpan.FromSeconds(opts.Timeout <= 0
            ? OcspPeerConfig.DefaultOCSPResponderTimeout.TotalSeconds
            : opts.Timeout);

        var reqEnc = EncodeOCSPRequest(ocspRequest);

        using var hc = new HttpClient { Timeout = timeout };

        Exception? lastError = null;
        foreach (var endpoint in link.OcspWebEndpoints)
        {
            var responderUrl = endpoint.ToString().TrimEnd('/');
            log?.Debug(OcspMessages.DbgMakingCARequest, responderUrl);

            try
            {
                var url = $"{responderUrl}/{reqEnc}";
                using var response = await hc.GetAsync(url, cancellationToken).ConfigureAwait(false);
                if (!response.IsSuccessStatusCode)
                    throw new HttpRequestException(
                        string.Format(OcspMessages.ErrBadResponderHTTPStatus, (int)response.StatusCode));

                return await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                lastError = ex;
            }
        }

        throw new InvalidOperationException(
            string.Format(OcspMessages.ErrFailedWithAllRequests, lastError?.Message), lastError);
    }

    /// <summary>
    /// Base64-encodes the OCSP request DER bytes and URL-escapes the result
    /// for use as a path segment (RFC 6960 Appendix A.1).
    /// </summary>
    public static string EncodeOCSPRequest(byte[] reqDer) =>
        Uri.EscapeDataString(Convert.ToBase64String(reqDer));
}
