using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Grpc.Net.Client;
using Microsoft.Extensions.Logging;

namespace ZB.MOM.WW.LmxProxy.Client.Security;

/// <summary>
/// Factory for creating configured gRPC channels with TLS support.
/// </summary>
internal static class GrpcChannelFactory
{
#pragma warning disable CA1810 // Initialize reference type static fields inline
    static GrpcChannelFactory()
#pragma warning restore CA1810
    {
        // Enable HTTP/2 over plaintext for non-TLS scenarios
        AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    }

    /// <summary>
    /// Creates a <see cref="GrpcChannel"/> with the specified address, TLS configuration, and optional API key header.
    /// </summary>
    public static GrpcChannel CreateChannel(Uri address, ClientTlsConfiguration? tlsConfiguration, ILogger logger, string? apiKey = null)
    {
        var handler = new SocketsHttpHandler
        {
            EnableMultipleHttp2Connections = true
        };

        if (tlsConfiguration?.UseTls == true)
        {
            ConfigureTls(handler, tlsConfiguration, logger);
        }

        HttpMessageHandler finalHandler = handler;

        // Add API key header to all outgoing requests if provided
        if (!string.IsNullOrEmpty(apiKey))
        {
            finalHandler = new ApiKeyDelegatingHandler(apiKey, handler);
        }

        var channelOptions = new GrpcChannelOptions
        {
            HttpHandler = finalHandler
        };

        logger.LogDebug("Creating gRPC channel to {Address}, TLS={UseTls}", address, tlsConfiguration?.UseTls ?? false);
        return GrpcChannel.ForAddress(address, channelOptions);
    }

    /// <summary>
    /// DelegatingHandler that adds the x-api-key header to all outgoing requests.
    /// </summary>
    private sealed class ApiKeyDelegatingHandler : DelegatingHandler
    {
        private readonly string _apiKey;

        public ApiKeyDelegatingHandler(string apiKey, HttpMessageHandler innerHandler)
            : base(innerHandler)
        {
            _apiKey = apiKey;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.Headers.TryAddWithoutValidation("x-api-key", _apiKey);
            return base.SendAsync(request, cancellationToken);
        }
    }

    private static void ConfigureTls(SocketsHttpHandler handler, ClientTlsConfiguration tls, ILogger logger)
    {
        handler.SslOptions = new SslClientAuthenticationOptions
        {
            EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
        };

        if (!string.IsNullOrEmpty(tls.ServerNameOverride))
        {
            handler.SslOptions.TargetHost = tls.ServerNameOverride;
        }

        // Load client certificate for mTLS
        if (!string.IsNullOrEmpty(tls.ClientCertificatePath) && !string.IsNullOrEmpty(tls.ClientKeyPath))
        {
            var clientCert = X509Certificate2.CreateFromPemFile(tls.ClientCertificatePath, tls.ClientKeyPath);
            handler.SslOptions.ClientCertificates = [clientCert];
            logger.LogDebug("Loaded client certificate for mTLS from {Path}", tls.ClientCertificatePath);
        }

        // Certificate validation callback
        handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            if (tls.IgnoreAllCertificateErrors)
            {
                logger.LogWarning("Ignoring all certificate errors (IgnoreAllCertificateErrors=true)");
                return true;
            }

            if (!tls.ValidateServerCertificate)
            {
                return true;
            }

            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            // Custom CA trust store
            if (!string.IsNullOrEmpty(tls.ServerCaCertificatePath) && certificate is not null)
            {
                using var customChain = new X509Chain();
                customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                customChain.ChainPolicy.CustomTrustStore.Add(X509CertificateLoader.LoadCertificateFromFile(tls.ServerCaCertificatePath));
                if (customChain.Build(new X509Certificate2(certificate)))
                    return true;
            }

            if (tls.AllowSelfSignedCertificates && sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
            {
                logger.LogWarning("Allowing self-signed certificate");
                return true;
            }

            logger.LogError("Certificate validation failed: {Errors}", sslPolicyErrors);
            return false;
        };
    }
}
