using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using ApiDenuncias.Configuration;
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Options;

namespace ApiDenuncias.Services;

public sealed class ManualPurgeService
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    private readonly HttpClient _httpClient;
    private readonly KeyVaultOptions _keyVaultOptions;
    private readonly ManualPurgeOptions _manualPurgeOptions;
    private readonly ILogger<ManualPurgeService> _logger;

    public ManualPurgeService(
        HttpClient httpClient,
        IOptions<KeyVaultOptions> keyVaultOptions,
        IOptions<ManualPurgeOptions> manualPurgeOptions,
        ILogger<ManualPurgeService> logger)
    {
        _httpClient = httpClient;
        _keyVaultOptions = keyVaultOptions.Value;
        _manualPurgeOptions = manualPurgeOptions.Value;
        _logger = logger;
    }

    public async Task<ManualPurgeResponse> ExecuteAsync(DateOnly purgeDate, CancellationToken cancellationToken)
    {
        var configuredFunctionKey = Environment.GetEnvironmentVariable("KEYMGMT_FUNCTION_KEY");
        var hasDirectFunctionKey = !string.IsNullOrWhiteSpace(configuredFunctionKey);

        if (!_keyVaultOptions.Enabled && !hasDirectFunctionKey)
        {
            throw new InvalidOperationException("Key Vault debe estar habilitado o KEYMGMT_FUNCTION_KEY debe estar configurada para ejecutar una purga manual.");
        }

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

        var functionUri = ResolveEndpointUri(
            _manualPurgeOptions.FunctionUrl,
            "manual_purge",
            "ManualPurge:FunctionUrl");

        Uri? forceRotateUri = null;
        if (_manualPurgeOptions.ReplaceOnManualPurge && _manualPurgeOptions.RecoverPartialReplaceFailure)
        {
            forceRotateUri = ResolveEndpointUri(
                _manualPurgeOptions.ForceRotateUrl,
                "force_rotate",
                "ManualPurge:ForceRotateUrl");
        }

        if (!hasDirectFunctionKey && string.IsNullOrWhiteSpace(_manualPurgeOptions.FunctionKeySecretName))
        {
            throw new InvalidOperationException("ManualPurge:FunctionKeySecretName no esta configurado.");
        }

        var timeoutSeconds = Math.Clamp(_manualPurgeOptions.TimeoutSeconds, 5, 120);
        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeout.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));

        var dateText = purgeDate.ToString("yyyy-MM-dd");
        var functionKey = await ReadFunctionKeyAsync(timeout.Token);

        using var request = new HttpRequestMessage(HttpMethod.Post, functionUri);
        request.Headers.TryAddWithoutValidation("x-functions-key", functionKey);
        request.Content = JsonContent.Create(
            new { date = dateText, replace = _manualPurgeOptions.ReplaceOnManualPurge },
            options: JsonOptions);

        _logger.LogWarning(
            "Ejecutando purga manual mediante Function App. Date={Date}; Replace={Replace}; FunctionUrl={FunctionUrl}; SecretName={SecretName}",
            dateText,
            _manualPurgeOptions.ReplaceOnManualPurge,
            functionUri,
            _manualPurgeOptions.FunctionKeySecretName);

        using var response = await _httpClient.SendAsync(request, timeout.Token);
        var body = await response.Content.ReadAsStringAsync(timeout.Token);

        if (!response.IsSuccessStatusCode)
        {
            if (forceRotateUri is not null && IsPartialPurgeFailure(body))
            {
                return await RecoverWithForceRotateAsync(
                    forceRotateUri,
                    dateText,
                    functionKey,
                    body,
                    timeout.Token);
            }

            throw new InvalidOperationException(
                $"La Function App de purga ha respondido con {(int)response.StatusCode} ({response.ReasonPhrase}). Body: {TrimBody(body)}");
        }

        _logger.LogWarning(
            "Purga manual completada por Function App. Date={Date}; StatusCode={StatusCode}",
            dateText,
            (int)response.StatusCode);

        return new ManualPurgeResponse(
            dateText,
            Success: true,
            StatusCode: (int)response.StatusCode,
            ResponseBody: body);
    }

    private async Task<ManualPurgeResponse> RecoverWithForceRotateAsync(
        Uri forceRotateUri,
        string dateText,
        string functionKey,
        string purgeBody,
        CancellationToken cancellationToken)
    {
        _logger.LogWarning(
            "La purga manual se completo, pero fallo la creacion de la nueva clave. Reintentando force_rotate. Date={Date}; ForceRotateUrl={ForceRotateUrl}",
            dateText,
            forceRotateUri);

        using var request = new HttpRequestMessage(HttpMethod.Post, forceRotateUri);
        request.Headers.TryAddWithoutValidation("x-functions-key", functionKey);
        request.Content = JsonContent.Create(new { date = dateText }, options: JsonOptions);

        using var response = await _httpClient.SendAsync(request, cancellationToken);
        var body = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Conflict)
        {
            throw new InvalidOperationException(
                "La purga se completo, pero no se ha podido crear la nueva clave con force_rotate. " +
                $"Status={(int)response.StatusCode} ({response.ReasonPhrase}). Body: {TrimBody(body)}. " +
                $"Respuesta original manual_purge: {TrimBody(purgeBody)}");
        }

        _logger.LogWarning(
            "Recuperacion force_rotate completada tras purga manual parcial. Date={Date}; StatusCode={StatusCode}",
            dateText,
            (int)response.StatusCode);

        var recoveryBody = JsonSerializer.Serialize(
            new
            {
                message = "manual_purge purgo la clave, pero fallo al crear la nueva. La API ha reintentado force_rotate para la misma fecha.",
                manual_purge_body = TrimBody(purgeBody),
                force_rotate_status = (int)response.StatusCode,
                force_rotate_body = TrimBody(body)
            },
            JsonOptions);

        return new ManualPurgeResponse(
            dateText,
            Success: true,
            StatusCode: (int)response.StatusCode,
            ResponseBody: recoveryBody);
    }

    private async Task<string> ReadFunctionKeyAsync(CancellationToken cancellationToken)
    {
        var configuredFunctionKey = Environment.GetEnvironmentVariable("KEYMGMT_FUNCTION_KEY");
        if (!string.IsNullOrWhiteSpace(configuredFunctionKey))
        {
            return configuredFunctionKey.Trim();
        }

        try
        {
            var client = new SecretClient(new Uri(_keyVaultOptions.VaultUrl), new DefaultAzureCredential());
            var secret = await client.GetSecretAsync(
                _manualPurgeOptions.FunctionKeySecretName.Trim(),
                cancellationToken: cancellationToken);

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

            return secret.Value.Value.Trim();
        }
        catch (RequestFailedException ex)
        {
            throw new InvalidOperationException(
                $"No se ha podido leer el secreto '{_manualPurgeOptions.FunctionKeySecretName}' de Key Vault. Status={ex.Status}; Error={ex.ErrorCode}.",
                ex);
        }
    }

    private static string TrimBody(string body)
    {
        if (string.IsNullOrWhiteSpace(body))
        {
            return "(sin cuerpo)";
        }

        return body.Length <= 1200
            ? body
            : body[..1200] + "...";
    }

    private static bool IsPartialPurgeFailure(string body)
    {
        if (string.IsNullOrWhiteSpace(body))
        {
            return false;
        }

        try
        {
            using var document = JsonDocument.Parse(body);
            return document.RootElement.TryGetProperty("purged", out var purged) &&
                purged.ValueKind == JsonValueKind.True;
        }
        catch (JsonException)
        {
            return false;
        }
    }

    private Uri ResolveEndpointUri(string configuredUrl, string endpointName, string optionName)
    {
        var baseUrl = FirstNonWhiteSpace(
            Environment.GetEnvironmentVariable("KEYMGMT_BASE_URL"),
            _manualPurgeOptions.BaseUrl);

        var candidateUrl = string.IsNullOrWhiteSpace(baseUrl)
            ? configuredUrl
            : $"{baseUrl.Trim().TrimEnd('/')}/{endpointName}";

        if (!Uri.TryCreate(candidateUrl, UriKind.Absolute, out var uri))
        {
            var sourceName = string.IsNullOrWhiteSpace(baseUrl)
                ? optionName
                : "KEYMGMT_BASE_URL/ManualPurge:BaseUrl";
            throw new InvalidOperationException($"{sourceName} no contiene una URL valida.");
        }

        return uri;
    }

    private static string? FirstNonWhiteSpace(params string?[] values)
        => values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));
}
