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

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

/// <summary>
/// End-to-end guard for the Health dashboard KPI tiles (<c>/monitoring/health</c>).
///
/// <para>
/// The Health dashboard fans out to three Akka cluster-singleton <c>Ask</c>s every
/// 10 s (Notification Outbox, Site Call, Audit) to populate nine KPI tiles. A
/// previously-fixed bug caused those <c>Ask</c>s to hang, leaving every tile showing
/// the em-dash degrade placeholder (<c>—</c>) instead of a resolved numeric value.
/// This test guards that regression: it asserts that every tile resolves to a value
/// and never shows <c>—</c>.
/// </para>
///
/// <para>
/// The three tile groups and their selectors:
/// <list type="bullet">
///   <item>
///     <b>Notification Outbox</b> — inlined in <c>Health.razor</c> with no
///     <c>data-test</c> attribute. Each is a Bootstrap <c>div.card</c> whose
///     <c>.card-body</c> contains a value <c>h3</c> and a <c>small.text-muted</c>
///     label. Located by the card that contains the label text, then its <c>h3</c>.
///   </item>
///   <item>
///     <b>Audit</b> — rendered by <c>AuditKpiTiles.razor</c>; each tile button
///     carries a <c>data-test</c> attribute: <c>audit-kpi-volume</c>,
///     <c>audit-kpi-error-rate</c>, <c>audit-kpi-backlog</c>.
///   </item>
///   <item>
///     <b>Site Calls</b> — rendered by <c>SiteCallKpiTiles.razor</c>; each tile
///     button carries a <c>data-test</c> attribute: <c>site-call-kpi-buffered</c>,
///     <c>site-call-kpi-stuck</c>, <c>site-call-kpi-parked</c>.
///   </item>
/// </list>
/// </para>
/// </summary>
[Collection("Playwright")]
public class HealthDashboardTests
{
    private const string HealthUrl = "/monitoring/health";

    /// <summary>
    /// The degrade placeholder rendered when a KPI loader faults — an em-dash
    /// (U+2014). A healthy tile shows a non-negative integer instead.
    /// </summary>
    private const string DegradePlaceholder = "—"; // —

    private readonly PlaywrightFixture _fixture;

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

    /// <summary>
    /// Asserts that all nine KPI tiles on the Health dashboard resolve to numeric
    /// values and do not show the em-dash degrade placeholder (<c>—</c>).
    ///
    /// <para>
    /// A generous 20 s per-tile timeout is intentional: the tiles are populated
    /// asynchronously after initial render as the three singleton <c>Ask</c>s
    /// complete. The Playwright web-first assertion retries within that window
    /// rather than using a fixed sleep, so a fast cluster will pass quickly.
    /// </para>
    /// </summary>
    [SkippableFact]
    public async Task KpiTiles_ResolveToValues_NotDegradePlaceholder()
    {
        Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);

        var page = await _fixture.NewAuthenticatedPageAsync();
        await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{HealthUrl}");
        await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

        // ── Notification Outbox tiles (no data-test; inlined in Health.razor as
        //    plain div.card elements under the "Notification Outbox" h6 heading).
        //    Scope all three cards to the div.row that immediately follows the flex
        //    container holding the h6 — prevents false matches if a second section
        //    later grows cards with the same label text ("Stuck", "Parked").
        //
        //    Health.razor structure:
        //      <div class="d-flex ...">
        //        <h6 class="text-muted mb-0">Notification Outbox</h6>  ← anchor
        //        <a ...>View details →</a>
        //      </div>
        //      <div class="row g-3 mb-3">            ← outboxSection (+sibling)
        //        <div class="col-..."><div class="card">Queue Depth</div></div>
        //        <div class="col-..."><div class="card">Stuck</div></div>
        //        <div class="col-..."><div class="card">Parked</div></div>
        //      </div>
        var outboxSection = page.Locator("div.d-flex:has(h6:has-text('Notification Outbox')) + div.row");

        var queueDepthH3 = outboxSection.Locator("div.card", new() { HasText = "Queue Depth" }).Locator("h3");
        var outboxStuckH3 = outboxSection.Locator("div.card", new() { HasText = "Stuck" }).Locator("h3");
        var outboxParkedH3 = outboxSection.Locator("div.card", new() { HasText = "Parked" }).Locator("h3");

        await AssertTileResolvedAsync(queueDepthH3, "Outbox Queue Depth");
        await AssertTileResolvedAsync(outboxStuckH3, "Outbox Stuck");
        await AssertTileResolvedAsync(outboxParkedH3, "Outbox Parked");

        // ── Audit KPI tiles (AuditKpiTiles.razor — data-test on the button) ──

        var auditVolume = page.Locator("[data-test='audit-kpi-volume']").Locator("h3");
        var auditErrorRate = page.Locator("[data-test='audit-kpi-error-rate']").Locator("h3");
        var auditBacklog = page.Locator("[data-test='audit-kpi-backlog']").Locator("h3");

        await AssertTileResolvedAsync(auditVolume, "Audit Volume");
        await AssertTileResolvedAsync(auditErrorRate, "Audit Error Rate");
        await AssertTileResolvedAsync(auditBacklog, "Audit Backlog");

        // ── Site Call KPI tiles (SiteCallKpiTiles.razor — data-test on the button) ──

        var siteCallBuffered = page.Locator("[data-test='site-call-kpi-buffered']").Locator("h3");
        var siteCallStuck = page.Locator("[data-test='site-call-kpi-stuck']").Locator("h3");
        var siteCallParked = page.Locator("[data-test='site-call-kpi-parked']").Locator("h3");

        await AssertTileResolvedAsync(siteCallBuffered, "Site Call Buffered");
        await AssertTileResolvedAsync(siteCallStuck, "Site Call Stuck");
        await AssertTileResolvedAsync(siteCallParked, "Site Call Parked");
    }

    /// <summary>
    /// Waits up to 20 s for <paramref name="tileH3"/> to be non-empty and not
    /// equal to the em-dash degrade placeholder.  Uses Playwright web-first
    /// assertions so the retry loop is inside the Playwright engine, not a
    /// C# busy-wait.
    /// </summary>
    private static async Task AssertTileResolvedAsync(ILocator tileH3, string tileName)
    {
        // The tile must be visible and contain text before we check its value.
        await Assertions.Expect(tileH3)
            .ToBeVisibleAsync(new() { Timeout = 20_000 });

        // Primary guard: the value must NOT be the degrade placeholder.
        await Assertions.Expect(tileH3)
            .Not.ToHaveTextAsync(DegradePlaceholder, new() { Timeout = 20_000 });

        // Secondary guard: the value must be non-empty — it should be a digit string.
        await Assertions.Expect(tileH3)
            .Not.ToBeEmptyAsync(new() { Timeout = 20_000 });

        // Diagnostic: capture the resolved text for context in test output, but
        // don't fail on any particular number (the cluster state is environment-
        // dependent and any non-negative integer is valid).
        var resolvedText = await tileH3.TextContentAsync();
        Assert.True(
            resolvedText != null && resolvedText.Trim().Length > 0,
            $"KPI tile '{tileName}' resolved to null/empty content.");
        Assert.False(
            resolvedText!.Trim() == DegradePlaceholder,
            $"KPI tile '{tileName}' shows the degrade placeholder '—' — singleton Ask likely hung or faulted.");
    }
}
