using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
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;

internal static class GrpcChannelFactory
{
    private const string Http2UnencryptedSwitch = "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport";

    static GrpcChannelFactory()
    {
        AppContext.SetSwitch(Http2UnencryptedSwitch, true);
    }

    /// <summary>
    /// Creates a gRPC channel with optional TLS configuration.
    /// </summary>
    /// <param name="address">The server address.</param>
    /// <param name="tlsConfiguration">Optional TLS configuration.</param>
    /// <param name="logger">The logger.</param>
    /// <returns>A configured gRPC channel.</returns>
    public static GrpcChannel CreateChannel(Uri address, ClientTlsConfiguration? tlsConfiguration, ILogger logger)
    {
        var options = new GrpcChannelOptions
        {
            HttpHandler = CreateHttpHandler(tlsConfiguration, logger)
        };

        return GrpcChannel.ForAddress(address, options);
    }

    private static HttpMessageHandler CreateHttpHandler(ClientTlsConfiguration? tlsConfiguration, ILogger logger)
    {
        var handler = new SocketsHttpHandler
        {
            AutomaticDecompression = DecompressionMethods.None,
            AllowAutoRedirect = false,
            EnableMultipleHttp2Connections = true
        };

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

        return handler;
    }

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

        if (!string.IsNullOrWhiteSpace(tlsConfiguration.ServerNameOverride))
        {
            sslOptions.TargetHost = tlsConfiguration.ServerNameOverride;
        }

        if (!string.IsNullOrWhiteSpace(tlsConfiguration.ClientCertificatePath) &&
            !string.IsNullOrWhiteSpace(tlsConfiguration.ClientKeyPath))
        {
            try
            {
                var clientCertificate = X509Certificate2.CreateFromPemFile(
                    tlsConfiguration.ClientCertificatePath,
                    tlsConfiguration.ClientKeyPath);
                clientCertificate = new X509Certificate2(clientCertificate.Export(X509ContentType.Pfx));

                sslOptions.ClientCertificates ??= new X509CertificateCollection();
                sslOptions.ClientCertificates.Add(clientCertificate);
                logger.LogInformation("Configured client certificate for mutual TLS ({CertificatePath})", tlsConfiguration.ClientCertificatePath);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Failed to load client certificate from {CertificatePath}", tlsConfiguration.ClientCertificatePath);
            }
        }

        sslOptions.RemoteCertificateValidationCallback = (_, certificate, chain, sslPolicyErrors) =>
            ValidateServerCertificate(tlsConfiguration, logger, certificate, chain, sslPolicyErrors);
    }

    private static bool ValidateServerCertificate(
        ClientTlsConfiguration tlsConfiguration,
        ILogger logger,
        X509Certificate? certificate,
        X509Chain? chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (tlsConfiguration.IgnoreAllCertificateErrors)
        {
            logger.LogWarning("SECURITY WARNING: Ignoring all certificate validation errors for LmxProxy gRPC connection.");
            return true;
        }

        if (certificate is null)
        {
            logger.LogWarning("Server certificate was null.");
            return false;
        }

        if (!tlsConfiguration.ValidateServerCertificate)
        {
            logger.LogWarning("SECURITY WARNING: Server certificate validation disabled for LmxProxy gRPC connection.");
            return true;
        }

        X509Certificate2 certificate2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);

        if (!string.IsNullOrWhiteSpace(tlsConfiguration.ServerNameOverride))
        {
            string dnsName = certificate2.GetNameInfo(X509NameType.DnsName, forIssuer: false);
            if (!string.Equals(dnsName, tlsConfiguration.ServerNameOverride, StringComparison.OrdinalIgnoreCase))
            {
                logger.LogWarning("Server certificate subject '{Subject}' does not match expected host '{ExpectedHost}'",
                    dnsName, tlsConfiguration.ServerNameOverride);
                return false;
            }
        }

        using X509Chain validationChain = chain ?? new X509Chain();
        validationChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
        validationChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

        if (!string.IsNullOrWhiteSpace(tlsConfiguration.ServerCaCertificatePath) &&
            File.Exists(tlsConfiguration.ServerCaCertificatePath))
        {
            try
            {
                X509Certificate2 ca = LoadCertificate(tlsConfiguration.ServerCaCertificatePath);
                validationChain.ChainPolicy.CustomTrustStore.Add(ca);
                validationChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Failed to load CA certificate from {Path}", tlsConfiguration.ServerCaCertificatePath);
            }
        }

        if (tlsConfiguration.AllowSelfSignedCertificates)
        {
            validationChain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
        }

        bool isValid = validationChain.Build(certificate2);
        if (isValid)
        {
            return true;
        }

        if (tlsConfiguration.AllowSelfSignedCertificates &&
            validationChain.ChainStatus.All(status =>
                status.Status == X509ChainStatusFlags.UntrustedRoot ||
                status.Status == X509ChainStatusFlags.PartialChain))
        {
            logger.LogWarning("Accepting self-signed certificate for {Subject}", certificate2.Subject);
            return true;
        }

        string statusMessage = string.Join(", ", validationChain.ChainStatus.Select(s => s.Status));
        logger.LogWarning("Server certificate validation failed: {Status}", statusMessage);
        return false;
    }

    private static X509Certificate2 LoadCertificate(string path)
    {
        try
        {
            return X509Certificate2.CreateFromPemFile(path);
        }
        catch
        {
            return new X509Certificate2(File.ReadAllBytes(path));
        }
    }
}
