using Microsoft.Playwright;
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
using Xunit;

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

/// <summary>
/// E2E coverage for the tabbed, tree-rendered Debug View
/// (<c>/deployment/debug-view</c>) shipped by the debugview-tabs-trees feature.
/// Where <see cref="DebugViewTests"/> proves the controls render and Connect
/// resolves to a terminal state, this class proves the connected layout: a tab
/// strip (Attributes / Alarms), each tab a composition <see cref="TreeView{T}"/>.
///
/// <para>
/// <b>Seeding mirrors <see cref="DebugViewTests"/> exactly</b> — the Enabled-only
/// instance <c>&lt;select&gt;</c> (verified against
/// <c>DebugView.razor::LoadInstancesForSite</c>, <c>i.State == InstanceState.Enabled</c>)
/// lists only Enabled instances. A freshly-minted instance is <c>NotDeployed</c>
/// and would never appear, so the test mints an instance via the shared
/// <see cref="DeploymentFixture"/> and drives it to <c>Enabled</c> over the CLI
/// (<c>instance deploy</c>) before selecting it. The fixture provisions onto the
/// real, running <c>site-a</c> so the snapshot round-trip resolves
/// fast (an unknown site has no reachable gRPC endpoint and only times out).
/// </para>
///
/// <para>
/// <b>Tolerant terminal-state wait, then the tabs/trees assertions</b> — Connect
/// awaits a site snapshot round-trip; on success the page flips to the
/// Live badge and renders the tabbed card, on failure it surfaces a
/// <c>Connect failed: …</c> <c>.toast</c> (see <c>DebugView.razor::Connect</c>).
/// The test first waits on a TERMINAL state (Live badge OR error toast) within a
/// generous window so it never hangs, then asserts the Live badge directly
/// (OBSERVED reliable on this live cluster in <see cref="DebugViewTests"/>). Only
/// once connected does the card with the tab strip render
/// (<c>@if (_connected &amp;&amp; _snapshot != null)</c>), so the tabs/trees
/// assertions are gated behind the Live badge.
/// </para>
///
/// <para>
/// <b>Why the alarms-tab assertion is tolerant</b> — the
/// <see cref="TreeView{T}"/> renders its <c>&lt;ul role="tree"&gt;</c> root ONLY
/// when the forest is non-empty; when empty it renders the <c>EmptyContent</c>
/// slot ("No alarms.") with no <c>[role="tree"]</c> element. The seeded
/// <see cref="DeploymentFixture"/> instance has no configured alarms, so the
/// alarms pane shows the empty hint. The Attributes pane always has data (one
/// attribute is seeded), so its <c>[role="tree"]</c> assertion remains strict.
/// The populated alarm tree is covered by bUnit tests (DebugViewAlarmTableTests).
/// </para>
/// </summary>
[Collection("Playwright")]
public class DebugViewTreeTests : IClassFixture<DeploymentFixture>
{
    private readonly PlaywrightFixture _pw;
    private readonly DeploymentFixture _cluster;

    public DebugViewTreeTests(PlaywrightFixture pw, DeploymentFixture cluster)
    {
        _pw = pw;
        _cluster = cluster;
    }

    [SkippableFact]
    public async Task DebugView_Connected_RendersTabbedTrees_AndAlarmsTabSwitches()
    {
        Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);

        var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
        try
        {
            await CliRunner.DeployInstanceAsync(instanceId);   // -> Enabled, so it lists in the dropdown

            var page = await _pw.NewAuthenticatedPageAsync();
            await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/debug-view");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            // Select the seeded site + Enabled instance (same hooks as DebugViewTests).
            await page.Locator("[data-test='debug-site-select']")
                .SelectOptionAsync(new SelectOptionValue { Value = _cluster.SiteAId.ToString() });
            var instanceOption = page.Locator("[data-test='debug-instance-select'] option",
                new() { HasText = uniqueName });
            await Assertions.Expect(instanceOption).ToHaveCountAsync(1, new() { Timeout = 10_000 });
            await page.Locator("[data-test='debug-instance-select']")
                .SelectOptionAsync(new SelectOptionValue { Value = instanceId.ToString() });

            var connect = page.Locator("button.btn.btn-primary.btn-sm:has-text('Connect')");
            await Assertions.Expect(connect).ToBeEnabledAsync(new() { Timeout = 10_000 });
            await connect.ClickAsync();

            // Outcome-tolerant terminal-state floor: Live badge OR an error toast within a
            // generous window so the click never hangs (mirrors DebugViewTests). Then tighten
            // to the observed-reliable Live outcome — the tabbed card only renders when
            // connected, so the tabs/trees assertions below depend on reaching Live.
            var terminal = page.Locator("span.badge[aria-label='Connection state: Live'], .toast");
            await Assertions.Expect(terminal.First).ToBeVisibleAsync(new() { Timeout = 25_000 });
            await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
                .ToBeVisibleAsync(new() { Timeout = 2_000 });

            // ── Tab strip renders with both tabs (the tabs+trees rework's structural hooks). ──
            var attrTab = page.Locator("[data-test='debug-tab-attributes']");
            var alarmTab = page.Locator("[data-test='debug-tab-alarms']");
            await Assertions.Expect(attrTab).ToBeVisibleAsync(new() { Timeout = 10_000 });
            await Assertions.Expect(alarmTab).ToBeVisibleAsync();

            var attrPane = page.Locator("[data-test='debug-pane-attributes']");
            var alarmPane = page.Locator("[data-test='debug-pane-alarms']");

            // Attributes is the default tab: its pane is visible and hosts a tree;
            // the Alarms pane is present in the DOM but hidden (d-none) until selected.
            await Assertions.Expect(attrTab).ToHaveAttributeAsync("aria-selected", "true");
            await Assertions.Expect(attrPane).ToBeVisibleAsync();
            await Assertions.Expect(attrPane.Locator("[role='tree']")).ToHaveCountAsync(1);
            await Assertions.Expect(alarmPane).ToBeHiddenAsync();

            // ── Switch to the Alarms tab and assert the pane swaps + hosts its own tree. ──
            await alarmTab.ClickAsync();
            await Assertions.Expect(alarmTab).ToHaveAttributeAsync("aria-selected", "true");
            await Assertions.Expect(alarmPane).ToBeVisibleAsync(new() { Timeout = 10_000 });
            await Assertions.Expect(attrPane).ToBeHiddenAsync();
            // TreeView renders <ul role="tree"> ONLY when the forest is non-empty; when empty it
            // renders the EmptyContent slot ("No alarms.") instead. The seeded DeploymentFixture
            // instance has no configured alarms, so the alarms forest is empty and the pane shows
            // the empty hint rather than a tree. The populated alarm tree is covered by bUnit
            // tests (DebugViewAlarmTableTests). Here we prove the tab switch works and the pane
            // renders something valid — either a tree (if alarms exist) or the empty hint.
            var alarmTreeCount = await alarmPane.Locator("[role='tree']").CountAsync();
            if (alarmTreeCount == 0)
            {
                // No alarms configured on the seeded instance → TreeView shows EmptyContent.
                await Assertions.Expect(alarmPane).ToContainTextAsync("No alarms.");
            }

            // Disconnect tears the card down and re-enables the selectors.
            await page.Locator("button.btn-outline-danger.btn-sm:has-text('Disconnect')").ClickAsync();
            await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Disconnected']"))
                .ToBeVisibleAsync(new() { Timeout = 10_000 });
            await Assertions.Expect(page.Locator("[data-test='debug-site-select']")).ToBeEnabledAsync();
        }
        finally
        {
            await CliRunner.DeleteInstanceAsync(instanceId);
        }
    }
}
