using ApiDenuncias.Configuration;
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Options;
using MySqlConnector;

namespace ApiDenuncias.Services;

public sealed class MySqlConnectionStringProvider
{
    private readonly ComplaintStorageOptions _storageOptions;
    private readonly KeyVaultOptions _keyVaultOptions;
    private readonly ILogger<MySqlConnectionStringProvider> _logger;
    private readonly SemaphoreSlim _loadGate = new(1, 1);
    private string? _cachedConnectionString;

    public MySqlConnectionStringProvider(
        IOptions<ComplaintStorageOptions> storageOptions,
        IOptions<KeyVaultOptions> keyVaultOptions,
        ILogger<MySqlConnectionStringProvider> logger)
    {
        _storageOptions = storageOptions.Value;
        _keyVaultOptions = keyVaultOptions.Value;
        _logger = logger;
    }

    public async ValueTask<string> GetConnectionStringAsync(CancellationToken cancellationToken = default)
    {
        if (!string.IsNullOrWhiteSpace(_cachedConnectionString))
        {
            return _cachedConnectionString;
        }

        await _loadGate.WaitAsync(cancellationToken);
        try
        {
            if (string.IsNullOrWhiteSpace(_cachedConnectionString))
            {
                _cachedConnectionString = await LoadConnectionStringAsync(cancellationToken);
            }

            return _cachedConnectionString;
        }
        finally
        {
            _loadGate.Release();
        }
    }

    private async Task<string> LoadConnectionStringAsync(CancellationToken cancellationToken)
    {
        if (!_storageOptions.UseKeyVault || !_keyVaultOptions.Enabled)
        {
            if (string.IsNullOrWhiteSpace(_storageOptions.ConnectionString))
            {
                throw new InvalidOperationException(
                    "Falta configurar ComplaintStorage:ConnectionString o activar Key Vault para obtener la conexion MySQL.");
            }

            _logger.LogWarning("Conexion MySQL cargada desde appsettings. Usar solo para desarrollo/local.");
            return _storageOptions.ConnectionString;
        }

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

        var client = new SecretClient(new Uri(_keyVaultOptions.VaultUrl), new DefaultAzureCredential());
        var host = await GetRequiredSecretAsync(client, _storageOptions.HostSecretName, cancellationToken);
        var user = await GetRequiredSecretAsync(client, _storageOptions.UserSecretName, cancellationToken);
        var password = await GetRequiredSecretAsync(client, _storageOptions.PasswordSecretName, cancellationToken);
        var database = await GetRequiredSecretAsync(client, _storageOptions.DatabaseSecretName, cancellationToken);
        var port = await GetOptionalUIntSecretAsync(client, _storageOptions.PortSecretName, _storageOptions.DefaultPort, cancellationToken);
        var sslMode = await GetOptionalSecretAsync(client, _storageOptions.SslModeSecretName, _storageOptions.DefaultSslMode, cancellationToken);

        var builder = new MySqlConnectionStringBuilder
        {
            Server = host,
            Port = port,
            UserID = user,
            Password = password,
            Database = database,
            SslMode = ParseSslMode(sslMode),
        };

        _logger.LogInformation(
            "Conexion MySQL cargada desde Key Vault {VaultUrl}. Host={Host}; Database={Database}; User={User}; Port={Port}.",
            _keyVaultOptions.VaultUrl,
            host,
            database,
            user,
            port);

        return builder.ConnectionString;
    }

    private static async Task<string> GetRequiredSecretAsync(
        SecretClient client,
        string secretName,
        CancellationToken cancellationToken)
    {
        var value = await GetOptionalSecretAsync(client, secretName, null, cancellationToken);
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException($"El secreto obligatorio '{secretName}' de Key Vault no existe o esta vacio.");
        }

        return value.Trim();
    }

    private static async Task<string> GetOptionalSecretAsync(
        SecretClient client,
        string secretName,
        string? fallback,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(secretName))
        {
            return fallback ?? string.Empty;
        }

        try
        {
            var secret = await client.GetSecretAsync(secretName.Trim(), cancellationToken: cancellationToken);
            return string.IsNullOrWhiteSpace(secret.Value.Value)
                ? fallback ?? string.Empty
                : secret.Value.Value.Trim();
        }
        catch (RequestFailedException ex) when (ex.Status == StatusCodes.Status404NotFound)
        {
            return fallback ?? string.Empty;
        }
    }

    private static async Task<uint> GetOptionalUIntSecretAsync(
        SecretClient client,
        string secretName,
        uint fallback,
        CancellationToken cancellationToken)
    {
        var value = await GetOptionalSecretAsync(client, secretName, null, cancellationToken);
        return uint.TryParse(value, out var parsed) && parsed > 0
            ? parsed
            : fallback;
    }

    private static MySqlSslMode ParseSslMode(string? value)
    {
        return Enum.TryParse<MySqlSslMode>(value, ignoreCase: true, out var parsed)
            ? parsed
            : MySqlSslMode.Required;
    }
}
