using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NATS.Server.TestUtilities;
using NATS.Server.Tls;

namespace NATS.Server.Transport.Tests;

public class TlsOcspParityBatch2Tests
{
    [Fact]
    public void CertOCSPEligible_returns_true_and_populates_endpoints_for_http_ocsp_aia()
    {
        using var cert = CreateLeafWithOcspAia("http://ocsp.example.test");
        var link = new ChainLink { Leaf = cert };

        var eligible = TlsHelper.CertOCSPEligible(link);

        eligible.ShouldBeTrue();
        link.OCSPWebEndpoints.ShouldNotBeNull();
        link.OCSPWebEndpoints!.Count.ShouldBe(1);
        link.OCSPWebEndpoints[0].ToString().ShouldBe("http://ocsp.example.test/");
    }

    [Fact]
    public void CertOCSPEligible_returns_false_when_leaf_has_no_ocsp_servers()
    {
        var (leaf, _) = TestCertHelper.GenerateTestCert();
        var link = new ChainLink { Leaf = leaf };

        TlsHelper.CertOCSPEligible(link).ShouldBeFalse();
    }

    [Fact]
    public void GetLeafIssuerCert_returns_positional_issuer_or_null()
    {
        using var root = CreateRootCertificate();
        using var leaf = CreateLeafSignedBy(root);

        var chain = new[] { leaf, root };
        TlsHelper.GetLeafIssuerCert(chain, 0).ShouldBe(root);
        TlsHelper.GetLeafIssuerCert(chain, 1).ShouldBeNull();
        TlsHelper.GetLeafIssuerCert(chain, -1).ShouldBeNull();
    }

    [Fact]
    public void GetLeafIssuer_returns_verified_issuer_from_chain()
    {
        using var root = CreateRootCertificate();
        using var leaf = CreateLeafSignedBy(root);

        using var issuer = TlsHelper.GetLeafIssuer(leaf, root);

        issuer.ShouldNotBeNull();
        issuer!.Thumbprint.ShouldBe(root.Thumbprint);
    }

    [Fact]
    public void OcspResponseCurrent_applies_skew_and_ttl_rules()
    {
        var opts = OCSPPeerConfig.NewOCSPPeerConfig();
        var now = DateTime.UtcNow;

        TlsHelper.OcspResponseCurrent(new OcspResponseInfo
        {
            ThisUpdate = now.AddMinutes(-1),
            NextUpdate = now.AddMinutes(5),
        }, opts).ShouldBeTrue();

        TlsHelper.OcspResponseCurrent(new OcspResponseInfo
        {
            ThisUpdate = now.AddHours(-2),
            NextUpdate = null,
        }, opts).ShouldBeFalse();

        TlsHelper.OcspResponseCurrent(new OcspResponseInfo
        {
            ThisUpdate = now.AddMinutes(2),
            NextUpdate = now.AddHours(1),
        }, opts).ShouldBeFalse();
    }

    [Fact]
    public void ValidDelegationCheck_accepts_direct_and_ocsp_signing_delegate()
    {
        using var issuer = CreateRootCertificate();
        using var delegateCert = CreateOcspSigningDelegate(issuer);

        TlsHelper.ValidDelegationCheck(issuer, null).ShouldBeTrue();
        TlsHelper.ValidDelegationCheck(issuer, issuer).ShouldBeTrue();
        TlsHelper.ValidDelegationCheck(issuer, delegateCert).ShouldBeTrue();
    }

    [Fact]
    public void OcspPeerMessages_exposes_error_and_debug_constants()
    {
        OcspPeerMessages.ErrIllegalPeerOptsConfig.ShouldContain("expected map to define OCSP peer options");
        OcspPeerMessages.ErrNoAvailOCSPServers.ShouldBe("no available OCSP servers");
        OcspPeerMessages.DbgPlugTLSForKind.ShouldBe("Plugging TLS OCSP peer for [%s]");
        OcspPeerMessages.DbgCacheSaved.ShouldBe("Saved OCSP peer cache successfully (%d bytes)");
        OcspPeerMessages.MsgFailedOCSPResponseFetch.ShouldBe("Failed OCSP response fetch");
    }

    private static X509Certificate2 CreateLeafWithOcspAia(string ocspUri)
    {
        using var key = RSA.Create(2048);
        var req = new CertificateRequest("CN=leaf-with-ocsp", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
        req.CertificateExtensions.Add(CreateOcspAiaExtension(ocspUri));
        return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
    }

    private static X509Extension CreateOcspAiaExtension(string ocspUri)
    {
        var writer = new AsnWriter(AsnEncodingRules.DER);
        writer.PushSequence();
        writer.PushSequence();
        writer.WriteObjectIdentifier("1.3.6.1.5.5.7.48.1");
        writer.WriteCharacterString(UniversalTagNumber.IA5String, ocspUri, new Asn1Tag(TagClass.ContextSpecific, 6));
        writer.PopSequence();
        writer.PopSequence();
        return new X509Extension("1.3.6.1.5.5.7.1.1", writer.Encode(), false);
    }

    private static X509Certificate2 CreateRootCertificate()
    {
        using var rootKey = RSA.Create(2048);
        var req = new CertificateRequest("CN=Root", rootKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
        req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
        return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(5));
    }

    private static X509Certificate2 CreateLeafSignedBy(X509Certificate2 issuer)
    {
        using var leafKey = RSA.Create(2048);
        var req = new CertificateRequest("CN=Leaf", leafKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
        req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));

        var cert = req.Create(
            issuer,
            DateTimeOffset.UtcNow.AddDays(-1),
            DateTimeOffset.UtcNow.AddYears(1),
            Guid.NewGuid().ToByteArray());

        return cert.CopyWithPrivateKey(leafKey);
    }

    private static X509Certificate2 CreateOcspSigningDelegate(X509Certificate2 issuer)
    {
        using var key = RSA.Create(2048);
        var req = new CertificateRequest("CN=OCSP Delegate", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
        req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
            [new Oid("1.3.6.1.5.5.7.3.9")], true));

        var cert = req.Create(
            issuer,
            DateTimeOffset.UtcNow.AddDays(-1),
            DateTimeOffset.UtcNow.AddYears(1),
            Guid.NewGuid().ToByteArray());

        return cert.CopyWithPrivateKey(key);
    }
}
