using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Auth;

/// <summary>
/// Regression tests for CentralUI-017. <c>POST /auth/logout</c> called
/// <c>.DisableAntiforgery()</c> and a plain <c>GET /logout</c> route also
/// signed the user out — either could be triggered cross-site to forcibly log
/// a user out. Logout is a state-changing authenticated action and must be
/// CSRF-protected: the POST keeps antiforgery enabled and the state-changing
/// GET route is removed.
/// </summary>
public class AuthEndpointsCsrfTests
{
    private static IReadOnlyList<RouteEndpoint> BuildEndpoints()
    {
        var builder = WebApplication.CreateBuilder();
        builder.Services.AddRouting();
        builder.Services.AddAntiforgery();
        // Dispose the host: an undisposed WebApplication leaks its config
        // PhysicalFileProvider (appsettings reload-watch FileSystemWatcher — a
        // process-wide macOS run-loop thread) and a ConsoleLoggerProcessor
        // thread, which keep the test host process alive after the run.
        using var app = builder.Build();
        app.MapAuthEndpoints();

        return ((IEndpointRouteBuilder)app).DataSources
            .SelectMany(ds => ds.Endpoints)
            .OfType<RouteEndpoint>()
            .ToList();
    }

    private static RouteEndpoint? Find(IReadOnlyList<RouteEndpoint> endpoints, string pattern, string method)
        => endpoints.FirstOrDefault(e =>
            e.RoutePattern.RawText == pattern &&
            (e.Metadata.GetMetadata<HttpMethodMetadata>()?.HttpMethods.Contains(method) ?? false));

    [Fact]
    public void PostAuthLogout_DoesNotDisableAntiforgery()
    {
        var endpoints = BuildEndpoints();
        var logout = Find(endpoints, "/auth/logout", "POST");

        Assert.NotNull(logout);
        // DisableAntiforgery() leaves an IAntiforgeryMetadata with
        // RequiresValidation == false. A CSRF-protected POST has either no such
        // metadata, or metadata that still requires validation.
        var antiforgery = logout!.Metadata.GetMetadata<IAntiforgeryMetadata>();
        Assert.True(antiforgery is null || antiforgery.RequiresValidation,
            "POST /auth/logout must keep antiforgery validation enabled.");
    }

    [Fact]
    public void GetLogout_StateChangingRoute_IsRemoved()
    {
        var endpoints = BuildEndpoints();
        var getLogout = Find(endpoints, "/logout", "GET");

        Assert.Null(getLogout);
    }

    [Fact]
    public void PostAuthLogin_StillDisablesAntiforgery_PreAuthIsAcceptable()
    {
        // Login is a pre-auth endpoint; disabling antiforgery there is acceptable
        // and intentional. This pins that the fix did not over-correct.
        var endpoints = BuildEndpoints();
        var login = Find(endpoints, "/auth/login", "POST");

        Assert.NotNull(login);
        var antiforgery = login!.Metadata.GetMetadata<IAntiforgeryMetadata>();
        Assert.NotNull(antiforgery);
        Assert.False(antiforgery!.RequiresValidation);
    }
}
