using System.Security.Cryptography;
using Microsoft.Extensions.Options;
using Tiku.Api.Options;

namespace Tiku.Api.Middleware;

public sealed class BrowserCsrfMiddleware(
    RequestDelegate next,
    IOptions<BrowserAuthOptions> options)
{
    public async Task InvokeAsync(HttpContext context)
    {
        if (!IsUnsafe(context.Request.Method) || !IsBrowserCookieRequest(context.Request))
        {
            await next(context);
            return;
        }

        if (!IsTrustedOrigin(context.Request) || !HasValidCsrfToken(context.Request))
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsJsonAsync(new
            {
                title = "Browser request origin or CSRF token is invalid.",
                status = StatusCodes.Status403Forbidden,
                code = "browser_csrf_rejected",
                traceId = context.TraceIdentifier
            }, context.RequestAborted);
            return;
        }

        await next(context);
    }

    private static bool IsUnsafe(string method) =>
        !HttpMethods.IsGet(method) && !HttpMethods.IsHead(method) && !HttpMethods.IsOptions(method);

    private static bool IsBrowserCookieRequest(HttpRequest request) =>
        request.Cookies.ContainsKey(BrowserAuthOptions.AccessCookie) ||
        request.Cookies.ContainsKey(BrowserAuthOptions.RefreshCookie);

    private bool IsTrustedOrigin(HttpRequest request)
    {
        var origin = request.Headers.Origin.ToString().Trim().TrimEnd('/');
        if (string.IsNullOrWhiteSpace(origin) || !Uri.TryCreate(origin, UriKind.Absolute, out var uri))
        {
            return false;
        }

        var sameOrigin = string.Equals(uri.Scheme, request.Scheme, StringComparison.OrdinalIgnoreCase) &&
                         string.Equals(uri.Authority, request.Host.Value, StringComparison.OrdinalIgnoreCase);
        return sameOrigin || options.Value.AllowedOrigins.Any(allowed =>
            string.Equals(allowed.Trim().TrimEnd('/'), origin, StringComparison.OrdinalIgnoreCase));
    }

    private static bool HasValidCsrfToken(HttpRequest request)
    {
        var cookie = request.Cookies[BrowserAuthOptions.CsrfCookie];
        var header = request.Headers[BrowserAuthOptions.CsrfHeader].ToString();
        if (string.IsNullOrWhiteSpace(cookie) || string.IsNullOrWhiteSpace(header))
        {
            return false;
        }
        var left = System.Text.Encoding.UTF8.GetBytes(cookie);
        var right = System.Text.Encoding.UTF8.GetBytes(header);
        return left.Length == right.Length && CryptographicOperations.FixedTimeEquals(left, right);
    }
}
