using Microsoft.AspNetCore.Components.Web.HtmlRendering;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Alarms;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
using ZB.MOM.WW.MxGateway.Server.Dashboard.Components.Pages;
using ZB.MOM.WW.MxGateway.Tests.TestSupport;
using HtmlRenderer = Microsoft.AspNetCore.Components.Web.HtmlRenderer;

namespace ZB.MOM.WW.MxGateway.Tests.Dashboard;

/// <summary>
///     Renders <see cref="AlarmsPage"/> and asserts the truncated-snapshot
///     caveat banner appears exactly when the alarm query reports a capped
///     provider fetch.
/// </summary>
/// <remarks>
///     <para>
///         The absence assertion is the load-bearing one: a banner that renders
///         unconditionally would satisfy the positive case while telling every
///         operator, on every normal day, that the alarm list might be missing
///         alarms. A caveat that is always on is a caveat nobody reads.
///     </para>
///     <para>
///         Static rendering via the framework's <see cref="HtmlRenderer"/>, as in
///         <c>SecretsNavRenderTests</c> — the assertion is about markup the server
///         emits, so no component-testing dependency is warranted. The page's
///         poll loop runs its first pass inline during <c>OnInitialized</c>
///         (the stub query completes synchronously), so the rendered markup
///         already reflects the query result.
///     </para>
/// </remarks>
public sealed class AlarmsPageTruncationBannerTests
{
    private const string BannerMarker = "Alarm snapshot may be incomplete";

    /// <summary>A capped provider fetch puts the completeness caveat on the page.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task AlarmsPage_WhenSnapshotTruncated_RendersTheCaveatBanner()
    {
        string html = await RenderAsync(snapshotTruncated: true);

        Assert.Contains(BannerMarker, html, StringComparison.Ordinal);
    }

    /// <summary>
    ///     The proof the banner is gated. A complete fetch must render no caveat
    ///     at all, while the page itself still renders — the alarm-table heading
    ///     is the control that keeps this from passing over a blank page.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task AlarmsPage_WhenSnapshotComplete_OmitsTheCaveatBanner()
    {
        string html = await RenderAsync(snapshotTruncated: false);

        Assert.DoesNotContain(BannerMarker, html, StringComparison.Ordinal);
        Assert.Contains("Active Alarms", html, StringComparison.Ordinal);
    }

    private static async Task<string> RenderAsync(bool snapshotTruncated)
    {
        ServiceCollection services = new();
        services.AddLogging();
        services.AddSingleton<IDashboardLiveDataService>(
            new StubLiveDataService(snapshotTruncated));
        services.AddSingleton<IGatewayAlarmService>(
            new FakeGatewayAlarmService { SnapshotTruncated = snapshotTruncated });
        services.AddSingleton<IOptions<GatewayOptions>>(
            Options.Create(new GatewayOptions { Alarms = new AlarmsOptions { Enabled = true } }));

        await using ServiceProvider provider = services.BuildServiceProvider();
        await using HtmlRenderer renderer = new(
            provider,
            provider.GetRequiredService<ILoggerFactory>());

        return await renderer.Dispatcher.InvokeAsync(async () =>
        {
            HtmlRootComponent output = await renderer.RenderComponentAsync<AlarmsPage>();
            return output.ToHtmlString();
        });
    }

    // Answers the page's 3-second poll synchronously, so the first pass completes
    // inline inside OnInitialized and the rendered markup reflects it.
    private sealed class StubLiveDataService(bool snapshotTruncated) : IDashboardLiveDataService
    {
        /// <inheritdoc />
        public Task<DashboardLiveReadResult> ReadAsync(
            IReadOnlyCollection<string> tagAddresses,
            CancellationToken cancellationToken) =>
            Task.FromResult(DashboardLiveReadResult.Empty);

        /// <inheritdoc />
        public Task<DashboardAlarmQueryResult> QueryAlarmsAsync(CancellationToken cancellationToken) =>
            Task.FromResult(new DashboardAlarmQueryResult(
                Alarms: [],
                Error: null,
                WorkerProcessId: null,
                SnapshotTruncated: snapshotTruncated));
    }
}
