using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;

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

public sealed class OcspPeerValidationTests : IDisposable
{
    private readonly List<X509Certificate2> _certs = [];

    [Fact]
    public void ParseOCSPPeer_ValidMap_ReturnsConfig()
    {
        Dictionary<string, object?> map = new()
        {
            ["verify"] = true,
            ["allowed_clockskew"] = 30.0,
            ["ca_timeout"] = 5.0,
            ["cache_ttl_when_next_update_unset"] = 120.0,
            ["warn_only"] = true,
            ["unknown_is_good"] = true,
            ["allow_when_ca_unreachable"] = true,
        };

        var (config, err) = OcspHandler.ParseOCSPPeer(map);

        err.ShouldBeNull();
        config.ShouldNotBeNull();
        config.Verify.ShouldBeTrue();
        config.ClockSkew.ShouldBe(30.0);
        config.Timeout.ShouldBe(5.0);
        config.TTLUnsetNextUpdate.ShouldBe(120.0);
        config.WarnOnly.ShouldBeTrue();
        config.UnknownIsGood.ShouldBeTrue();
        config.AllowWhenCAUnreachable.ShouldBeTrue();
    }

    [Fact]
    public void PeerFromVerifiedChains_EmptyChains_ReturnsNull()
    {
        OcspHandler.PeerFromVerifiedChains([]).ShouldBeNull();
    }

    [Fact]
    public void PeerFromVerifiedChains_NonEmptyChains_ReturnsFirstLeaf()
    {
        var cert = CreateSelfSignedCertificate("CN=peer-first");

        var result = OcspHandler.PeerFromVerifiedChains([[cert]]);

        result.ShouldNotBeNull();
        result!.Subject.ShouldBe(cert.Subject);
    }

    [Fact]
    public void PlugTLSOCSPPeer_ClientWithoutVerify_ReturnsError()
    {
        var server = NewServer();
        var cert = CreateSelfSignedCertificate("CN=client-no-verify");
        var config = new OcspTlsConfig
        {
            Kind = "client",
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
            TlsOptions = new TlsConfigOpts
            {
                Verify = false,
                OcspPeerConfig = new OcspPeerConfig { Verify = true },
            },
            Apply = _ => { },
        };

        var (_, plugged, err) = server.PlugTLSOCSPPeer(config);

        plugged.ShouldBeFalse();
        err.ShouldNotBeNull();
        err!.Message.ShouldContain("mTLS");
    }

    [Fact]
    public void PlugTLSOCSPPeer_ClientWithVerify_ReturnsPlugged()
    {
        var server = NewServer();
        var cert = CreateSelfSignedCertificate("CN=client-verify");
        var config = new OcspTlsConfig
        {
            Kind = "client",
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
            TlsOptions = new TlsConfigOpts
            {
                Verify = true,
                OcspPeerConfig = new OcspPeerConfig { Verify = true },
            },
            Apply = _ => { },
        };

        var (tlsConfig, plugged, err) = server.PlugTLSOCSPPeer(config);

        err.ShouldBeNull();
        plugged.ShouldBeTrue();
        tlsConfig.ShouldNotBeNull();
        tlsConfig!.RemoteCertificateValidationCallback.ShouldNotBeNull();
    }

    [Fact]
    public void PlugTLSOCSPPeer_LeafSpoke_ReturnsPlugged()
    {
        var server = NewServer();
        var cert = CreateSelfSignedCertificate("CN=leaf-spoke");
        var config = new OcspTlsConfig
        {
            Kind = "leaf",
            IsLeafSpoke = true,
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
            TlsOptions = new TlsConfigOpts
            {
                Verify = true,
                OcspPeerConfig = new OcspPeerConfig { Verify = true },
            },
            Apply = _ => { },
        };

        var (_, plugged, err) = server.PlugTLSOCSPPeer(config);

        err.ShouldBeNull();
        plugged.ShouldBeTrue();
    }

    [Fact]
    public void TlsServerOCSPValid_SelfSignedChain_ReturnsTrue()
    {
        var server = NewServer();
        var cert = CreateSelfSignedCertificate("CN=selfsigned");
        var opts = new OcspPeerConfig { Verify = true };

        var valid = server.TlsServerOCSPValid([[cert]], opts);

        valid.ShouldBeTrue();
    }

    [Fact]
    public void TlsClientOCSPValid_EmptyChains_ReturnsFalse()
    {
        var server = NewServer();
        var opts = new OcspPeerConfig { Verify = true };

        var valid = server.TlsClientOCSPValid([], opts);

        valid.ShouldBeFalse();
    }

    [Fact]
    public void CertOCSPGood_EmptyChainLink_ReturnsFalse()
    {
        var server = NewServer();
        var (reason, good) = server.CertOCSPGood(new ChainLink(), new OcspPeerConfig());

        good.ShouldBeFalse();
        reason.ShouldContain("Empty chainlink");
    }

    public void Dispose()
    {
        foreach (var cert in _certs)
        {
            cert.Dispose();
        }
    }

    private NatsServer NewServer()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions());
        err.ShouldBeNull();
        return server!;
    }

    private X509Certificate2 CreateSelfSignedCertificate(string subject)
    {
        var request = new CertificateRequest(
            subject,
            RSA.Create(2048),
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);
        request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
        request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));

        var cert = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
        _certs.Add(cert);
        return cert;
    }
}
