using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using Shouldly;
using ZB.MOM.NatsNet.Server;

namespace ZB.MOM.NatsNet.Server.Tests;

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

    [Fact]
    public void SetupOCSPStapleStoreDir_WithStoreDir_CreatesDirectory()
    {
        var dir = MakeTempDir();
        var server = NewServer(new ServerOptions
        {
            StoreDir = dir,
        });

        var err = server.SetupOCSPStapleStoreDir();

        err.ShouldBeNull();
        Directory.Exists(Path.Combine(dir, "ocsp")).ShouldBeTrue();
    }

    [Fact]
    public void ConfigureOCSP_WithTlsConfig_ReturnsClientEntry()
    {
        var cert = CreateSelfSignedCertificate("CN=configure-ocsp");
        var server = NewServer(new ServerOptions
        {
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
        });

        var configs = server.ConfigureOCSP();

        configs.Count.ShouldBe(1);
        configs[0].Kind.ShouldBe("client");
    }

    [Fact]
    public void NewOCSPMonitor_OcspNever_ReturnsNoMonitor()
    {
        var cert = CreateSelfSignedCertificate("CN=monitor-never");
        var server = NewServer(new ServerOptions
        {
            StoreDir = MakeTempDir(),
            OcspConfig = new OcspConfig { Mode = ZB.MOM.NatsNet.Server.OcspMode.Never },
        });

        var config = new OcspTlsConfig
        {
            Kind = "client",
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
            TlsOptions = null,
            Apply = _ => { },
        };

        var (_, monitor, err) = server.NewOCSPMonitor(config);

        err.ShouldBeNull();
        monitor.ShouldBeNull();
    }

    [Fact]
    public void EnableOCSP_WithAlwaysMode_AddsMonitor()
    {
        var cert = CreateSelfSignedCertificate("CN=enable-ocsp");
        var storeDir = MakeTempDir();
        WriteLocalOcspStatus(storeDir, cert);
        var server = NewServer(new ServerOptions
        {
            StoreDir = storeDir,
            OcspConfig = new OcspConfig
            {
                Mode = ZB.MOM.NatsNet.Server.OcspMode.Always,
                OverrideUrls = ["https://ocsp.example.test"],
            },
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
        });

        var err = server.EnableOCSP();

        err.ShouldBeNull();
        server.GetOcspMonitors().Length.ShouldBe(1);
    }

    [Fact]
    public void StartOCSPMonitoring_NoMonitors_DoesNotThrow()
    {
        var server = NewServer(new ServerOptions());

        Should.NotThrow(() => server.StartOCSPMonitoring());
    }

    [Fact]
    public void ReloadOCSP_WithConfiguredTls_ReplacesMonitors()
    {
        var cert = CreateSelfSignedCertificate("CN=reload-ocsp");
        var storeDir = MakeTempDir();
        WriteLocalOcspStatus(storeDir, cert);
        var server = NewServer(new ServerOptions
        {
            StoreDir = storeDir,
            OcspConfig = new OcspConfig
            {
                Mode = ZB.MOM.NatsNet.Server.OcspMode.Always,
                OverrideUrls = ["https://ocsp.example.test"],
            },
            TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
        });
        server.EnableOCSP().ShouldBeNull();
        server.GetOcspMonitors().Length.ShouldBe(1);

        var err = server.ReloadOCSP();

        err.ShouldBeNull();
        server.GetOcspMonitors().Length.ShouldBe(1);
    }

    [Fact]
    public void HasOCSPStatusRequest_CertificateWithoutExtension_ReturnsFalse()
    {
        var cert = CreateSelfSignedCertificate("CN=no-status-request");

        OcspHandler.HasOCSPStatusRequest(cert).ShouldBeFalse();
    }

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

        foreach (var dir in _tempDirs)
        {
            try { Directory.Delete(dir, recursive: true); } catch { }
        }
    }

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

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

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

    private string MakeTempDir()
    {
        var path = Path.Combine(Path.GetTempPath(), "nats-ocsp-" + Path.GetRandomFileName());
        Directory.CreateDirectory(path);
        _tempDirs.Add(path);
        return path;
    }

    private static void WriteLocalOcspStatus(string storeDir, X509Certificate2 cert)
    {
        var key = Convert.ToHexString(SHA256.HashData(cert.RawData)).ToLowerInvariant();
        var ocspDir = Path.Combine(storeDir, "ocsp");
        Directory.CreateDirectory(ocspDir);
        var payload = JsonSerializer.SerializeToUtf8Bytes(new
        {
            Status = 0,
            ThisUpdate = DateTime.UtcNow.AddMinutes(-5),
            NextUpdate = DateTime.UtcNow.AddHours(6),
        });
        File.WriteAllBytes(Path.Combine(ocspDir, key), payload);
    }
}
