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

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

/// <summary>
/// bUnit tests for the third <c>DialogHost</c> dialog kind (T33a):
/// <see cref="IDialogService.ShowAsync{TResult}"/> renders arbitrary custom
/// body content, omits the standard Cancel/Confirm footer (the body supplies
/// its own buttons), and resolves a typed result through the
/// <see cref="DialogContext{TResult}"/> the body is handed. The tests render a
/// real <see cref="DialogService"/> behind a live <c>DialogHost</c> so the
/// interface contract is exercised end to end.
///
/// bUnit cannot run real JS interop, so the host's <c>sbDialog.*</c> focus-trap
/// / focus-restoration calls are exercised in <see cref="JSRuntimeMode.Loose"/>
/// mode where they become no-ops.
/// </summary>
public class DialogHostShowAsyncTests : BunitContext
{
    private readonly DialogService _service = new();

    public DialogHostShowAsyncTests()
    {
        // Register the concrete service as the interface so DialogHost (which
        // injects IDialogService and casts to DialogService) resolves the same
        // instance the test drives.
        Services.AddSingleton<IDialogService>(_service);
        // Focus trap / restoration / body-lock interop are no-ops under test.
        JSInterop.Mode = JSRuntimeMode.Loose;
    }

    [Fact]
    public async Task ShowAsync_RendersCustomBody_AndResolvesTypedResult()
    {
        var cut = Render<DialogHost>();

        // Open a custom dialog whose body renders a single button that closes
        // the dialog with a typed string result via the supplied context.
        Task<string?> pending = null!;
        await cut.InvokeAsync(() =>
        {
            pending = _service.ShowAsync<string>("Title", ctx => builder =>
            {
                builder.OpenElement(0, "button");
                builder.AddAttribute(1, "data-test", "ok");
                builder.AddAttribute(2, "onclick",
                    EventCallback.Factory.Create(this, () => ctx.Close("done")));
                builder.CloseElement();
            });
        });
        cut.Render();

        // The custom body renders, and the standard footer is NOT present.
        Assert.NotNull(cut.Find("button[data-test='ok']"));
        Assert.DoesNotContain(cut.FindAll("button"),
            b => b.TextContent.Trim() is "Cancel" or "Confirm");

        Assert.False(pending.IsCompleted, "Dialog must stay open until the body acts.");

        // Clicking the custom button resolves the awaited task with the value.
        cut.Find("button[data-test='ok']").Click();

        var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
        Assert.Same(pending, completed);
        Assert.Equal("done", await pending);
    }

    [Fact]
    public async Task ShowAsync_CancelResolvesNull()
    {
        var cut = Render<DialogHost>();

        Task<string?> pending = null!;
        await cut.InvokeAsync(() =>
        {
            pending = _service.ShowAsync<string>("Title", ctx => builder =>
            {
                builder.OpenElement(0, "button");
                builder.AddAttribute(1, "data-test", "cancel");
                builder.AddAttribute(2, "onclick",
                    EventCallback.Factory.Create(this, () => ctx.Cancel()));
                builder.CloseElement();
            });
        });
        cut.Render();

        cut.Find("button[data-test='cancel']").Click();

        var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
        Assert.Same(pending, completed);
        Assert.Null(await pending);
    }

    [Fact]
    public async Task ShowAsync_EscapeResolvesNull()
    {
        var cut = Render<DialogHost>();

        Task<string?> pending = null!;
        await cut.InvokeAsync(() =>
        {
            pending = _service.ShowAsync<string>("Title", _ => builder =>
            {
                builder.OpenElement(0, "span");
                builder.AddContent(1, "no buttons");
                builder.CloseElement();
            });
        });
        cut.Render();

        // The host's Escape handler cancels custom dialogs the same way.
        cut.Find("div.modal").KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Key = "Escape" });

        var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
        Assert.Same(pending, completed);
        Assert.Null(await pending);
    }

    [Fact]
    public async Task ShowAsync_AppliesSizeClassToModalDialog()
    {
        var cut = Render<DialogHost>();

        await cut.InvokeAsync(() =>
        {
            _ = _service.ShowAsync<string>("Title", _ => builder =>
            {
                builder.AddContent(0, "body");
            }, size: "modal-lg");
        });
        cut.Render();

        var dialog = cut.Find("div.modal-dialog");
        Assert.Contains("modal-lg", dialog.ClassList);
    }

    [Fact]
    public async Task Backdrop_HasTokenHookClass()
    {
        var cut = Render<DialogHost>();

        await cut.InvokeAsync(() =>
        {
            _ = _service.ShowAsync<string>("Title", _ => builder =>
            {
                builder.AddContent(0, "body");
            });
        });
        cut.Render();

        // The backdrop carries the tokenized hook class so a later task can
        // attach the actual var-driven background rule without touching markup.
        var backdrop = cut.Find("div.modal-backdrop");
        Assert.Contains("sb-modal-backdrop", backdrop.ClassList);
    }

    [Fact]
    public async Task Confirm_StillResolvesTrue_NoRegression()
    {
        var cut = Render<DialogHost>();

        Task<bool> pending = null!;
        await cut.InvokeAsync(() => { pending = _service.ConfirmAsync("Title", "Sure?"); });
        cut.Render();

        // The standard footer's Confirm button is present for Confirm-kind dialogs.
        cut.FindAll("button").First(b => b.TextContent.Trim() == "Confirm").Click();

        var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
        Assert.Same(pending, completed);
        Assert.True(await pending);
    }
}
