using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace MemberCenter.Infrastructure.Configuration;

public static class CertificateLoader
{
    public static void LogExpirationWarning(
        ILogger logger,
        string name,
        X509Certificate2? certificate,
        int warningDays)
    {
        if (certificate is null)
        {
            return;
        }

        var remaining = certificate.NotAfter.ToUniversalTime() - DateTime.UtcNow;
        if (remaining <= TimeSpan.FromDays(Math.Clamp(warningDays, 1, 180)))
        {
            logger.LogWarning(
                "Certificate {CertificateName} ({Thumbprint}) expires at {ExpiresAtUtc} ({RemainingDays:F1} days remaining).",
                name,
                certificate.Thumbprint,
                certificate.NotAfter.ToUniversalTime(),
                remaining.TotalDays);
        }
    }

    public static IReadOnlyList<X509Certificate2> LoadPreviousFromConfiguration(
        IConfiguration configuration,
        string sectionPath)
    {
        return configuration.GetSection($"{sectionPath}:Previous")
            .GetChildren()
            .Select(section => LoadFromConfiguration(configuration, section.Path, required: true)!)
            .ToArray();
    }

    public static X509Certificate2? LoadFromConfiguration(
        IConfiguration configuration,
        string sectionPath,
        bool required)
    {
        var path = configuration[$"{sectionPath}:Path"]?.Trim();
        var password = configuration[$"{sectionPath}:Password"];

        if (string.IsNullOrWhiteSpace(path))
        {
            if (required)
            {
                throw new InvalidOperationException($"{sectionPath}:Path is required outside Development.");
            }

            return null;
        }

        if (!File.Exists(path))
        {
            throw new InvalidOperationException($"Certificate file configured by {sectionPath}:Path does not exist: {path}");
        }

        X509Certificate2 certificate;
        try
        {
            certificate = new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Certificate configured by {sectionPath} could not be loaded.", ex);
        }

        var now = DateTime.UtcNow;
        if (!certificate.HasPrivateKey)
        {
            certificate.Dispose();
            throw new InvalidOperationException($"Certificate configured by {sectionPath} must contain a private key.");
        }

        if (now < certificate.NotBefore.ToUniversalTime() || now >= certificate.NotAfter.ToUniversalTime())
        {
            certificate.Dispose();
            throw new InvalidOperationException($"Certificate configured by {sectionPath} is not currently valid.");
        }

        return certificate;
    }
}
