using System.Security.Cryptography;
using System.Text;
using ApiDenuncias.Configuration;
using Azure;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Options;

namespace ApiDenuncias.Services;

public sealed class KeyVaultEncryptionKeyProvider : IEncryptionKeyProvider
{
    private readonly KeyVaultOptions _options;
    private readonly IConfiguration _configuration;
    private readonly ILogger<KeyVaultEncryptionKeyProvider> _logger;
    private readonly Lazy<Task<byte[]>> _keyLoader;

    public KeyVaultEncryptionKeyProvider(
        IOptions<KeyVaultOptions> options,
        IConfiguration configuration,
        ILogger<KeyVaultEncryptionKeyProvider> logger)
    {
        _options = options.Value;
        _configuration = configuration;
        _logger = logger;
        _keyLoader = new Lazy<Task<byte[]>>(LoadKeyAsync);
    }

    public async ValueTask<byte[]> GetKeyAsync(CancellationToken cancellationToken = default)
    {
        var key = await _keyLoader.Value.WaitAsync(cancellationToken);
        return key.ToArray();
    }

    private async Task<byte[]> LoadKeyAsync()
    {
        var configuredLocalKey = _configuration["Encryption:LocalDevelopmentKey"];
        if (!_options.Enabled)
        {
            if (string.IsNullOrWhiteSpace(configuredLocalKey))
            {
                throw new InvalidOperationException(
                    "Key Vault esta deshabilitado y no se ha configurado Encryption:LocalDevelopmentKey.");
            }

            _logger.LogWarning("Key Vault deshabilitado. Usando clave local solo para pruebas de desarrollo.");
            return NormalizeKey(configuredLocalKey);
        }

        if (string.IsNullOrWhiteSpace(_options.VaultUrl))
        {
            throw new InvalidOperationException("KeyVault:VaultUrl no esta configurado.");
        }

        if (string.IsNullOrWhiteSpace(_options.EncryptionKeySecretName))
        {
            throw new InvalidOperationException("KeyVault:EncryptionKeySecretName no esta configurado.");
        }
        var credential = new DefaultAzureCredential();
        var client = new SecretClient(new Uri(_options.VaultUrl), credential);
        KeyVaultSecret secret;
        try
        {
            var response = await client.GetSecretAsync(_options.EncryptionKeySecretName);
            secret = response.Value;
        }
        catch (RequestFailedException ex) when (ex.Status == StatusCodes.Status404NotFound && _options.AllowLocalEncryptionKeyFallback)
        {
            if (string.IsNullOrWhiteSpace(configuredLocalKey))
            {
                throw new InvalidOperationException(
                    $"El secreto '{_options.EncryptionKeySecretName}' no existe en Key Vault y no se ha configurado Encryption:LocalDevelopmentKey.");
            }

            _logger.LogWarning(
                "El secreto {SecretName} no existe en Key Vault. Usando clave local temporal por AllowLocalEncryptionKeyFallback=true. No usar en produccion real.",
                _options.EncryptionKeySecretName);
            return NormalizeKey(configuredLocalKey);
        }

        if (string.IsNullOrWhiteSpace(secret.Value))
        {
            throw new InvalidOperationException(
                $"El secreto '{_options.EncryptionKeySecretName}' de Key Vault esta vacio.");
        }

        _logger.LogInformation(
            "Clave de cifrado cargada desde Key Vault {VaultUrl} usando el secreto {SecretName}.",
            _options.VaultUrl,
            _options.EncryptionKeySecretName);

        return NormalizeKey(secret.Value);
    }

    private static byte[] NormalizeKey(string secretValue)
    {
        var trimmed = secretValue.Trim();

        try
        {
            var base64Key = Convert.FromBase64String(trimmed);
            if (base64Key.Length is 16 or 24 or 32)
            {
                return base64Key;
            }
        }
        catch (FormatException)
        {
            // Si no es base64, derivamos una clave estable desde el valor textual.
        }

        return SHA256.HashData(Encoding.UTF8.GetBytes(trimmed));
    }
}
