using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace Tiku.Infrastructure.Commerce;

public sealed class TenantSecretEncryptionOptions
{
    public const string SectionName = "Security:TenantSecrets";
    public const string DevelopmentMasterKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";

    public string KeyId { get; set; } = string.Empty;

    public string MasterKey { get; set; } = string.Empty;

    public static bool BeValid(TenantSecretEncryptionOptions options)
    {
        if (string.IsNullOrWhiteSpace(options.KeyId) || string.IsNullOrWhiteSpace(options.MasterKey))
        {
            return false;
        }

        try
        {
            return Convert.FromBase64String(options.MasterKey).Length == 32;
        }
        catch (FormatException)
        {
            return false;
        }
    }

    public static bool IsDevelopmentDefault(TenantSecretEncryptionOptions options) =>
        string.Equals(options.MasterKey, DevelopmentMasterKey, StringComparison.Ordinal);
}

public interface ITenantSecretProtector
{
    ProtectedTenantSecret Protect(Guid tenantId, string secretRef, JsonElement payload);

    JsonElement Unprotect(
        Guid tenantId,
        string secretRef,
        string keyId,
        byte[] ciphertext,
        byte[] nonce,
        byte[] tag);
}

public sealed record ProtectedTenantSecret(
    string KeyId,
    byte[] Ciphertext,
    byte[] Nonce,
    byte[] Tag);

public sealed class TenantSecretProtector(
    Microsoft.Extensions.Options.IOptions<TenantSecretEncryptionOptions> options) : ITenantSecretProtector
{
    private readonly TenantSecretEncryptionOptions options = options.Value;

    public ProtectedTenantSecret Protect(Guid tenantId, string secretRef, JsonElement payload)
    {
        var key = Convert.FromBase64String(options.MasterKey);
        var plaintext = JsonSerializer.SerializeToUtf8Bytes(payload);
        var nonce = RandomNumberGenerator.GetBytes(12);
        var ciphertext = new byte[plaintext.Length];
        var tag = new byte[16];
        var associatedData = GetAssociatedData(tenantId, secretRef, options.KeyId);

        try
        {
            using var aes = new AesGcm(key, tag.Length);
            aes.Encrypt(nonce, plaintext, ciphertext, tag, associatedData);
            return new ProtectedTenantSecret(options.KeyId, ciphertext, nonce, tag);
        }
        finally
        {
            CryptographicOperations.ZeroMemory(key);
            CryptographicOperations.ZeroMemory(plaintext);
        }
    }

    public JsonElement Unprotect(
        Guid tenantId,
        string secretRef,
        string keyId,
        byte[] ciphertext,
        byte[] nonce,
        byte[] tag)
    {
        if (!string.Equals(keyId, options.KeyId, StringComparison.Ordinal))
        {
            throw new InvalidOperationException(
                $"Tenant secret uses unknown encryption key '{keyId}'.");
        }

        var key = Convert.FromBase64String(options.MasterKey);
        var plaintext = new byte[ciphertext.Length];
        var associatedData = GetAssociatedData(tenantId, secretRef, keyId);
        try
        {
            using var aes = new AesGcm(key, tag.Length);
            aes.Decrypt(nonce, ciphertext, tag, plaintext, associatedData);
            using var document = JsonDocument.Parse(plaintext);
            return document.RootElement.Clone();
        }
        finally
        {
            CryptographicOperations.ZeroMemory(key);
            CryptographicOperations.ZeroMemory(plaintext);
        }
    }

    private static byte[] GetAssociatedData(Guid tenantId, string secretRef, string keyId) =>
        Encoding.UTF8.GetBytes($"{tenantId:N}\n{secretRef}\n{keyId}");
}
