using Microsoft.Playwright;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests;

/// <summary>
/// E2E tests for the collapsible sidebar nav sections, as implemented by the
/// ZB.MOM.WW.Theme kit (NavRailSection). Each section is a native
/// <c>&lt;details class="rail-section"&gt;</c> with a
/// <c>&lt;summary class="rail-eyebrow-toggle"&gt;</c> toggle whose
/// <c>aria-expanded</c> mirrors the open state. Sections are <b>expanded by
/// default</b>; clicking a header toggles it; the open/closed state persists in
/// <c>localStorage</c> (key <c>zbnav:&lt;sectionKey&gt;</c>) across a full reload;
/// and the section holding the active link is auto-revealed on arrival.
///
/// <para>The kit nav is a static-SSR / CSS-only design. ScadaBridge's Central UI
/// renders under global <c>@rendermode InteractiveServer</c>, where the kit's
/// collapse is currently non-functional — Blazor's management of the native
/// <c>&lt;details&gt;</c> defeats the content-hiding and <c>nav-state.js</c> never
/// wires the live DOM (no <c>data-zbnav-initialized</c>), so aria sync, localStorage
/// persistence, and active-reveal are inert. See themeissues.md Issue 6 in the
/// ZB.MOM.WW.Theme repo. The three behavior tests below assert the <i>corrected</i>
/// behavior and are <see cref="SkippableFactAttribute"/>-skipped until the kit fix
/// (hide-when-closed CSS + interactive re-wire) is built and the cluster redeployed,
/// at which point they run and must pass.</para>
/// </summary>
[Collection("Playwright")]
public class NavCollapseTests
{
    private const string KitNavSkipReason =
        "ZB.MOM.WW.Theme collapsible nav is non-functional under interactive Blazor render " +
        "(see themeissues.md Issue 6); skipped pending the kit fix + cluster redeploy.";

    private readonly PlaywrightFixture _fixture;

    public NavCollapseTests(PlaywrightFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public async Task Sections_AreExpandedByDefault_AfterLogin()
    {
        var page = await _fixture.NewAuthenticatedPageAsync();

        // On a fresh context there is no saved state, so every section renders
        // expanded (NavRailSection defaults Expanded=true) — no section toggle
        // reports aria-expanded="false". This holds regardless of whether the kit's
        // JS collapse is wired, so it is a plain (non-skippable) fact.
        await Expect(page.Locator("summary.rail-eyebrow-toggle[aria-expanded='false']"))
            .ToHaveCountAsync(0);
        // A sectioned link is therefore visible without any expansion.
        await Expect(page.Locator("a.rail-link:has-text('Topology')")).ToBeVisibleAsync();
    }

    [SkippableFact]
    public async Task ClickingSectionHeader_TogglesItsItems()
    {
        var page = await _fixture.NewAuthenticatedPageAsync();
        Skip.IfNot(await NavCollapseWiredAsync(page), KitNavSkipReason);

        var toggle = page.Locator("summary.rail-eyebrow-toggle:has-text('Deployment')");
        var topology = page.Locator("a.rail-link:has-text('Topology')");

        // Starts expanded.
        await Expect(toggle).ToHaveAttributeAsync("aria-expanded", "true");
        await Expect(topology).ToBeVisibleAsync();

        // Clicking the header collapses the section and hides its items.
        await toggle.ClickAsync();
        await Expect(toggle).ToHaveAttributeAsync("aria-expanded", "false");
        await Expect(topology).Not.ToBeVisibleAsync();

        // Clicking again re-expands it.
        await toggle.ClickAsync();
        await Expect(toggle).ToHaveAttributeAsync("aria-expanded", "true");
        await Expect(topology).ToBeVisibleAsync();
    }

    [SkippableFact]
    public async Task CollapseState_SurvivesPageReload()
    {
        var page = await _fixture.NewAuthenticatedPageAsync();
        Skip.IfNot(await NavCollapseWiredAsync(page), KitNavSkipReason);

        var toggle = page.Locator("summary.rail-eyebrow-toggle:has-text('Deployment')");
        await toggle.ClickAsync();
        await Expect(toggle).ToHaveAttributeAsync("aria-expanded", "false");

        await page.ReloadAsync();
        await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

        // nav-state.js restored the collapsed Deployment section from the
        // zbnav:deployment localStorage entry. (The dashboard has no active link
        // inside Deployment, so the active-reveal does not re-open it.)
        await Expect(page.Locator("summary.rail-eyebrow-toggle:has-text('Deployment')"))
            .ToHaveAttributeAsync("aria-expanded", "false");
        await Expect(page.Locator("a.rail-link:has-text('Topology')")).Not.ToBeVisibleAsync();
    }

    [SkippableFact]
    public async Task NavigatingIntoCollapsedSection_AutoExpandsIt()
    {
        var page = await _fixture.NewAuthenticatedPageAsync();
        Skip.IfNot(await NavCollapseWiredAsync(page), KitNavSkipReason);

        var auditToggle = page.Locator("summary.rail-eyebrow-toggle:has-text('Audit')");

        // Collapse the Audit section and confirm the preference is persisted.
        await Expect(auditToggle).ToHaveAttributeAsync("aria-expanded", "true");
        await auditToggle.ClickAsync();
        await Expect(auditToggle).ToHaveAttributeAsync("aria-expanded", "false");

        // Navigate to a route whose link lives in the now-collapsed Audit section.
        // On load the kit restores Audit collapsed from localStorage, then the
        // active-link reveal force-opens it so the nav shows where the user is.
        await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log");
        await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

        await Expect(page.Locator("summary.rail-eyebrow-toggle:has-text('Audit')"))
            .ToHaveAttributeAsync("aria-expanded", "true");
        await Expect(page.Locator("details.rail-section a.rail-link.active"))
            .ToBeVisibleAsync();

        // The reveal is transient: it must NOT overwrite the user's saved collapse
        // preference, which stays "0" in localStorage.
        var saved = await page.EvaluateAsync<string?>(
            "() => window.localStorage.getItem('zbnav:audit')");
        Assert.Equal("0", saved);
    }

    /// <summary>
    /// Returns true once the kit's <c>nav-state.js</c> has wired the live (interactive)
    /// nav — i.e. every <c>details.rail-section</c> carries <c>data-zbnav-initialized</c>.
    /// That is the observable signal that the themeissues.md Issue 6 fix is deployed; until
    /// then the collapse is inert and the behavior tests skip. Polls briefly to allow the
    /// interactive circuit to re-wire after render.
    /// </summary>
    private static async Task<bool> NavCollapseWiredAsync(IPage page)
    {
        const string probe =
            "() => { const d = document.querySelectorAll('details.rail-section');" +
            " return d.length > 0 && [...d].every(x => x.hasAttribute('data-zbnav-initialized')); }";
        for (int i = 0; i < 20; i++)
        {
            if (await page.EvaluateAsync<bool>(probe)) return true;
            await page.WaitForTimeoutAsync(250);
        }
        return false;
    }

    private static ILocatorAssertions Expect(ILocator locator) =>
        Assertions.Expect(locator);
}
