// TLS certificate provider that supports atomic cert swapping for hot reload.
// New connections get the current certificate; existing connections keep their original.
// Reference: golang/nats-server/server/reload.go — tlsOption.Apply.

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

namespace NATS.Server.Tls;

/// <summary>
/// Thread-safe provider for TLS certificates that supports atomic swapping
/// during config reload. New connections retrieve the latest certificate via
/// <see cref="GetCurrentCertificate"/>; existing connections are unaffected.
/// </summary>
public sealed class TlsCertificateProvider : IDisposable
{
    private volatile X509Certificate2? _currentCert;
    private volatile SslServerAuthenticationOptions? _currentSslOptions;
    private int _version;

    /// <summary>
    /// Creates a new provider and loads the initial certificate from the given paths.
    /// </summary>
    public TlsCertificateProvider(string certPath, string? keyPath)
    {
        _currentCert = TlsHelper.LoadCertificate(certPath, keyPath);
    }

    /// <summary>
    /// Creates a provider from a pre-loaded certificate (for testing).
    /// </summary>
    public TlsCertificateProvider(X509Certificate2 cert)
    {
        _currentCert = cert;
    }

    /// <summary>
    /// Returns the current certificate. This is called for each new TLS handshake
    /// so that new connections always get the latest certificate.
    /// </summary>
    public X509Certificate2? GetCurrentCertificate() => _currentCert;

    /// <summary>
    /// Atomically swaps the current certificate with a newly loaded one.
    /// Returns the old certificate (caller may dispose it after existing connections drain).
    /// </summary>
    public X509Certificate2? SwapCertificate(string certPath, string? keyPath)
    {
        var newCert = TlsHelper.LoadCertificate(certPath, keyPath);
        return SwapCertificate(newCert);
    }

    /// <summary>
    /// Atomically swaps the current certificate with the provided one.
    /// Returns the old certificate.
    /// </summary>
    public X509Certificate2? SwapCertificate(X509Certificate2 newCert)
    {
        var old = Interlocked.Exchange(ref _currentCert, newCert);
        Interlocked.Increment(ref _version);
        return old;
    }

    /// <summary>
    /// Returns the current SSL options, rebuilding them if the certificate has changed.
    /// </summary>
    public SslServerAuthenticationOptions? GetCurrentSslOptions() => _currentSslOptions;

    /// <summary>
    /// Atomically swaps the SSL server authentication options.
    /// Called after TLS config changes are detected during reload.
    /// </summary>
    public void SwapSslOptions(SslServerAuthenticationOptions newOptions)
    {
        Interlocked.Exchange(ref _currentSslOptions, newOptions);
        Interlocked.Increment(ref _version);
    }

    /// <summary>
    /// Monotonically increasing version number, incremented on each swap.
    /// Useful for tests to verify a reload occurred.
    /// </summary>
    public int Version => Volatile.Read(ref _version);

    public void Dispose()
    {
        _currentCert?.Dispose();
    }
}
