using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace NATS.Server.Tls;

public static class TlsHelper
{
    public static X509Certificate2 LoadCertificate(string certPath, string? keyPath)
    {
        if (keyPath != null)
            return X509Certificate2.CreateFromPemFile(certPath, keyPath);
        return X509CertificateLoader.LoadCertificateFromFile(certPath);
    }

    public static X509Certificate2Collection LoadCaCertificates(string caPath)
    {
        var collection = new X509Certificate2Collection();
        collection.ImportFromPemFile(caPath);
        return collection;
    }

    public static SslServerAuthenticationOptions BuildServerAuthOptions(NatsOptions opts)
    {
        var cert = LoadCertificate(opts.TlsCert!, opts.TlsKey);
        var authOpts = new SslServerAuthenticationOptions
        {
            ServerCertificate = cert,
            EnabledSslProtocols = opts.TlsMinVersion,
            ClientCertificateRequired = opts.TlsVerify,
        };

        if (opts.TlsVerify && opts.TlsCaCert != null)
        {
            var revocationMode = opts.OcspPeerVerify
                ? X509RevocationMode.Online
                : X509RevocationMode.NoCheck;

            var caCerts = LoadCaCertificates(opts.TlsCaCert);
            authOpts.RemoteCertificateValidationCallback = (_, cert, chain, errors) =>
            {
                if (cert == null) return false;
                using var chain2 = new X509Chain();
                chain2.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                foreach (var ca in caCerts)
                    chain2.ChainPolicy.CustomTrustStore.Add(ca);
                chain2.ChainPolicy.RevocationMode = revocationMode;
                var cert2 = cert as X509Certificate2 ?? X509CertificateLoader.LoadCertificate(cert.GetRawCertData());
                return chain2.Build(cert2);
            };
        }
        else if (opts.OcspPeerVerify)
        {
            // No custom CA — still enable online revocation checking against the system store
            authOpts.RemoteCertificateValidationCallback = (_, cert, chain, errors) =>
            {
                if (cert == null) return false;
                using var chain2 = new X509Chain();
                chain2.ChainPolicy.RevocationMode = X509RevocationMode.Online;
                var cert2 = cert as X509Certificate2 ?? X509CertificateLoader.LoadCertificate(cert.GetRawCertData());
                return chain2.Build(cert2);
            };
        }

        return authOpts;
    }

    /// <summary>
    /// Builds an <see cref="SslStreamCertificateContext"/> for OCSP stapling.
    /// Returns null when TLS is not configured or OCSP mode is Never.
    /// When <paramref name="offline"/> is false the runtime will contact the
    /// certificate's OCSP responder to obtain a fresh stapled response.
    /// </summary>
    public static SslStreamCertificateContext? BuildCertificateContext(NatsOptions opts, bool offline = false)
    {
        if (!opts.HasTls) return null;
        if (opts.OcspConfig is null || opts.OcspConfig.Mode == OcspMode.Never) return null;

        var cert = LoadCertificate(opts.TlsCert!, opts.TlsKey);
        var chain = new X509Certificate2Collection();
        if (!string.IsNullOrEmpty(opts.TlsCaCert))
            chain.ImportFromPemFile(opts.TlsCaCert);

        return SslStreamCertificateContext.Create(cert, chain, offline: offline);
    }

    public static string GetCertificateHash(X509Certificate2 cert)
    {
        var spki = cert.PublicKey.ExportSubjectPublicKeyInfo();
        var hash = SHA256.HashData(spki);
        return Convert.ToHexStringLower(hash);
    }

    public static bool MatchesPinnedCert(X509Certificate2 cert, HashSet<string> pinned)
    {
        var hash = GetCertificateHash(cert);
        return pinned.Contains(hash);
    }
}
