using Bunit;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;

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

/// <summary>
/// Regression tests for CentralUI-020 and CentralUI-025. <c>SessionExpiry</c>
/// used to poll the Blazor <c>AuthenticationStateProvider</c>, which (via
/// <c>CookieAuthenticationStateProvider</c>) serves a frozen constructor-time
/// principal — so the polled state could never become "expired" and the
/// idle-logout redirect never fired. The component now polls the server
/// <c>GET /auth/ping</c> endpoint, which reflects the live cookie session: a
/// 401 response triggers a redirect to <c>/login</c>. These tests exercise that
/// redirect path directly (CentralUI-025: the path was previously untested).
/// </summary>
public class SessionExpiryComponentTests : BunitContext
{
    private const string ModulePath = "./_content/ZB.MOM.WW.ScadaBridge.CentralUI/js/session-expiry.js";

    [Fact]
    public async Task CheckSession_ExpiredSession_RedirectsToLogin()
    {
        // The server reports the cookie has lapsed: ping returns HTTP 401.
        var module = JSInterop.SetupModule(ModulePath);
        module.Setup<int>("ping", "/auth/ping").SetResult(401);

        var nav = Services.GetRequiredService<NavigationManager>();
        var cut = Render<SessionExpiry>();

        await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());

        Assert.EndsWith("/login", nav.Uri);
    }

    [Fact]
    public async Task CheckSession_LiveSession_DoesNotRedirect()
    {
        // The server reports the session is still valid: ping returns HTTP 200.
        var module = JSInterop.SetupModule(ModulePath);
        module.Setup<int>("ping", "/auth/ping").SetResult(200);

        var nav = Services.GetRequiredService<NavigationManager>();
        var before = nav.Uri;
        var cut = Render<SessionExpiry>();

        await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());

        Assert.Equal(before, nav.Uri);
        Assert.DoesNotContain("/login", nav.Uri);
    }

    [Fact]
    public async Task CheckSession_TransientNetworkFailure_DoesNotRedirect()
    {
        // A network blip surfaces as status 0 — inconclusive. The component must
        // NOT log an authenticated user out on a transient failure.
        var module = JSInterop.SetupModule(ModulePath);
        module.Setup<int>("ping", "/auth/ping").SetResult(0);

        var nav = Services.GetRequiredService<NavigationManager>();
        var before = nav.Uri;
        var cut = Render<SessionExpiry>();

        await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());

        Assert.Equal(before, nav.Uri);
    }

    [Fact]
    public async Task CheckSession_OnLoginPage_DoesNotPingOrRedirect()
    {
        // On /login the component must neither poll nor redirect (a /login →
        // /login redirect would loop). JSInterop is left in Strict mode with no
        // module setup, so any ping call would throw and fail the test.
        var nav = (BunitNavigationManager)Services
            .GetRequiredService<NavigationManager>();
        nav.NavigateTo("login");

        var cut = Render<SessionExpiry>();
        await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());

        // No JS module import was attempted and the URL is unchanged.
        Assert.EndsWith("/login", nav.Uri);
    }
}
