using System.Security.Cryptography;
using System.Text;
using ApiDenuncias.Configuration;
using GestionaDenuncias.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;

namespace ApiDenuncias.Security;

public sealed class OpeBridgeApiKeyFilter : IAsyncActionFilter
{
    private readonly OpeBridgeOptions _options;
    private readonly ILogger<OpeBridgeApiKeyFilter> _logger;

    public OpeBridgeApiKeyFilter(
        IOptions<OpeBridgeOptions> options,
        ILogger<OpeBridgeApiKeyFilter> logger)
    {
        _options = options.Value;
        _logger = logger;
    }

    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        if (string.IsNullOrWhiteSpace(_options.ApiKey) || _options.ApiKey.Length < 32)
        {
            _logger.LogError("La clave interna de ApiOPE no esta configurada.");
            context.Result = new ObjectResult(new ApiError("El acceso interno OPE no esta configurado."))
            {
                StatusCode = StatusCodes.Status503ServiceUnavailable
            };
            return;
        }

        if (!context.HttpContext.Request.Headers.TryGetValue(_options.ApiKeyHeaderName, out var values) ||
            values.Count != 1 ||
            !FixedTimeEquals(values[0] ?? string.Empty, _options.ApiKey))
        {
            _logger.LogWarning(
                "Intento no autorizado de acceso interno OPE en {Path}.",
                context.HttpContext.Request.Path);
            context.Result = new UnauthorizedObjectResult(new ApiError("No autorizado."));
            return;
        }

        await next();
    }

    private static bool FixedTimeEquals(string supplied, string expected)
    {
        var suppliedHash = SHA256.HashData(Encoding.UTF8.GetBytes(supplied));
        var expectedHash = SHA256.HashData(Encoding.UTF8.GetBytes(expected));
        return CryptographicOperations.FixedTimeEquals(suppliedHash, expectedHash);
    }
}
