using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Server;

namespace foodmarket.Api.Infrastructure.Security;

/// <summary>Настройка ключей подписи/шифрования токенов OpenIddict.
///
/// <para><b>Development</b> — persistent RSA-ключ в App_Data/openiddict-dev-key.xml
/// (как было): переживает рестарты, шифрование access-token выключено для удобства
/// отладки. Поведение не меняется.</para>
///
/// <para><b>Production/Stage</b> — настоящие X509-сертификаты (отдельные для подписи
/// и шифрования). Путь берётся из конфигурации (<c>OpenIddict:SigningCertPath</c> /
/// <c>OpenIddict:EncryptionCertPath</c>, можно через env), иначе — App_Data/*.pfx
/// (монтируется volume'ом, см. docker-compose). Если файла нет — генерируется
/// <b>persistent</b> self-signed сертификат (5 лет) и сохраняется по пути. Это
/// заменяет dev-ephemeral поведение: при рестарте берётся тот же сертификат,
/// поэтому ранее выданные токены остаются валидными.</para>
///
/// <para>Опциональный пароль PFX — <c>OpenIddict:CertPassword</c>.</para>
/// </summary>
public static class OpenIddictKeyConfigurator
{
    private enum CertPurpose { Signing, Encryption }

    public static void ConfigureSigningAndEncryption(
        OpenIddictServerBuilder options, IConfiguration config, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            var rsa = LoadOrCreateDevRsa(
                Path.Combine(env.ContentRootPath, "App_Data", "openiddict-dev-key.xml"));
            var devKey = new RsaSecurityKey(rsa) { KeyId = "food-market-dev" };
            options.AddEncryptionKey(devKey);
            options.AddSigningKey(devKey);
            options.DisableAccessTokenEncryption();
            return;
        }

        var dataDir = Path.Combine(env.ContentRootPath, "App_Data");
        var password = config["OpenIddict:CertPassword"];

        var signingPath = config["OpenIddict:SigningCertPath"];
        if (string.IsNullOrWhiteSpace(signingPath))
            signingPath = Path.Combine(dataDir, "openiddict-signing.pfx");

        var encryptionPath = config["OpenIddict:EncryptionCertPath"];
        if (string.IsNullOrWhiteSpace(encryptionPath))
            encryptionPath = Path.Combine(dataDir, "openiddict-encryption.pfx");

        options.AddSigningCertificate(LoadOrCreateCertificate(signingPath, password, CertPurpose.Signing));
        options.AddEncryptionCertificate(LoadOrCreateCertificate(encryptionPath, password, CertPurpose.Encryption));
    }

    private static RSA LoadOrCreateDevRsa(string keyPath)
    {
        var rsa = RSA.Create(2048);
        if (File.Exists(keyPath))
        {
            rsa.FromXmlString(File.ReadAllText(keyPath));
        }
        else
        {
            Directory.CreateDirectory(Path.GetDirectoryName(keyPath)!);
            File.WriteAllText(keyPath, rsa.ToXmlString(includePrivateParameters: true));
        }
        return rsa;
    }

    private static X509Certificate2 LoadOrCreateCertificate(string path, string? password, CertPurpose purpose)
    {
        // EphemeralKeySet — приватный ключ держим в памяти процесса, не пишем
        // в системный keystore: в Linux-контейнере это самый переносимый вариант.
        const X509KeyStorageFlags flags = X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable;

        if (File.Exists(path))
        {
            return new X509Certificate2(File.ReadAllBytes(path), password, flags);
        }

        Directory.CreateDirectory(Path.GetDirectoryName(path)!);

        using var rsa = RSA.Create(2048);
        var request = new CertificateRequest(
            $"CN=food-market-{purpose.ToString().ToLowerInvariant()}",
            rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        var usage = purpose == CertPurpose.Signing
            ? X509KeyUsageFlags.DigitalSignature
            : X509KeyUsageFlags.KeyEncipherment;
        request.CertificateExtensions.Add(new X509KeyUsageExtension(usage, critical: true));

        using var generated = request.CreateSelfSigned(
            DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(5));

        var pfx = generated.Export(X509ContentType.Pfx, password);
        File.WriteAllBytes(path, pfx);
        return new X509Certificate2(pfx, password, flags);
    }
}
