using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;

namespace Predictalytics.Api.Security;

/// <summary>
/// Schuetzt die schreibenden Endpunkte mit einem gemeinsamen Token.
/// <para>
/// Solange Kestrel nur auf localhost lauscht, ist das Betriebssystem die Absicherung.
/// Sobald der Dienst von aussen erreichbar ist, faellt diese weg — dann muss jeder
/// Aufruf, der etwas veraendert, den Header <see cref="HeaderName"/> mitschicken.
/// </para>
/// </summary>
public sealed class ApiTokenFilter : IEndpointFilter
{
    public const string HeaderName = "X-Predictalytics-Key";

    private readonly byte[] _expected;

    public ApiTokenFilter(string token) => _expected = Encoding.UTF8.GetBytes(token);

    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
    {
        var provided = context.HttpContext.Request.Headers[HeaderName].ToString();

        if (!IsMatch(provided))
        {
            return Results.Json(
                new { error = "unauthorized", message = $"Header {HeaderName} fehlt oder ist falsch." },
                statusCode: StatusCodes.Status401Unauthorized);
        }

        return await next(context);
    }

    /// <summary>
    /// Laufzeitkonstanter Vergleich: ein zeichenweise abbrechender Vergleich verriete
    /// ueber die Antwortzeit, wie viele Zeichen bereits stimmen.
    /// </summary>
    private bool IsMatch(string provided)
    {
        if (string.IsNullOrEmpty(provided)) return false;
        var actual = Encoding.UTF8.GetBytes(provided);
        return CryptographicOperations.FixedTimeEquals(actual, _expected);
    }
}
