using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NATS.Client.Core;

namespace NATS.E2E.Tests.Infrastructure;

public sealed class TlsServerFixture : IAsyncLifetime
{
    private NatsServerProcess _server = null!;
    private string _tempDir = null!;

    public int Port => _server.Port;
    public string CaCertPath { get; private set; } = null!;

    public async Task InitializeAsync()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), $"nats-e2e-tls-{Guid.NewGuid():N}");
        Directory.CreateDirectory(_tempDir);

        var caCertPath = Path.Combine(_tempDir, "ca.pem");
        var serverCertPath = Path.Combine(_tempDir, "server-cert.pem");
        var serverKeyPath = Path.Combine(_tempDir, "server-key.pem");

        GenerateCertificates(caCertPath, serverCertPath, serverKeyPath);

        CaCertPath = caCertPath;

        var config = $$"""
            tls {
              cert_file: "{{serverCertPath}}"
              key_file: "{{serverKeyPath}}"
              ca_file: "{{caCertPath}}"
            }
            """;

        _server = NatsServerProcess.WithConfig(config);
        await _server.StartAsync();
    }

    public async Task DisposeAsync()
    {
        await _server.DisposeAsync();

        if (_tempDir is not null && Directory.Exists(_tempDir))
            Directory.Delete(_tempDir, recursive: true);
    }

    public NatsConnection CreateTlsClient()
    {
        var opts = new NatsOpts
        {
            Url = $"nats://127.0.0.1:{Port}",
            TlsOpts = new NatsTlsOpts
            {
                Mode = TlsMode.Require,
                InsecureSkipVerify = true,
            },
        };
        return new NatsConnection(opts);
    }

    public NatsConnection CreatePlainClient()
    {
        return new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{Port}" });
    }

    private static void GenerateCertificates(string caCertPath, string serverCertPath, string serverKeyPath)
    {
        // Generate CA key and self-signed certificate
        using var caKey = RSA.Create(2048);
        var caReq = new CertificateRequest(
            "CN=E2E Test CA",
            caKey,
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);
        caReq.CertificateExtensions.Add(
            new X509BasicConstraintsExtension(certificateAuthority: true, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: true));

        var now = DateTimeOffset.UtcNow;
        using var caCert = caReq.CreateSelfSigned(now.AddMinutes(-5), now.AddDays(1));

        // Generate server key and certificate signed by CA
        using var serverKey = RSA.Create(2048);
        var serverReq = new CertificateRequest(
            "CN=localhost",
            serverKey,
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);

        var sanBuilder = new SubjectAlternativeNameBuilder();
        sanBuilder.AddIpAddress(System.Net.IPAddress.Loopback);
        sanBuilder.AddDnsName("localhost");
        serverReq.CertificateExtensions.Add(sanBuilder.Build());
        serverReq.CertificateExtensions.Add(
            new X509BasicConstraintsExtension(certificateAuthority: false, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: false));

        using var serverCert = serverReq.Create(caCert, now.AddMinutes(-5), now.AddDays(1), [1, 2, 3, 4]);

        // Export CA cert to PEM
        File.WriteAllText(caCertPath, caCert.ExportCertificatePem());

        // Export server cert to PEM
        File.WriteAllText(serverCertPath, serverCert.ExportCertificatePem());

        // Export server private key to PEM
        File.WriteAllText(serverKeyPath, serverKey.ExportRSAPrivateKeyPem());
    }
}

[CollectionDefinition("E2E-TLS")]
public class TlsCollection : ICollectionFixture<TlsServerFixture>;
