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

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

/// <summary>
/// Full-UI round-trip coverage for Notification Lists (<c>/notifications/lists</c>).
///
/// <para>
/// This page requires the Design role; the test user <c>multi-role</c> has it. The
/// happy-path fact drives the entire lifecycle through the UI only — create the list,
/// open it for edit, add a recipient, remove that recipient, then delete the list and
/// assert the confirm-dialog + success-toast + row-gone sequence. A <c>finally</c> block
/// performs best-effort CLI teardown so no <c>zztest-notiflist-*</c> list leaks on failure.
/// The validation fact mutates nothing.
/// </para>
/// </summary>
[Collection("Playwright")]
public class NotificationListCrudTests
{
    private const string ListsUrl = "/notifications/lists";

    private readonly PlaywrightFixture _pw;

    public NotificationListCrudTests(PlaywrightFixture pw)
    {
        _pw = pw;
    }

    /// <summary>
    /// Drives the complete Notification List lifecycle through the UI: create the list,
    /// open it for edit, add a recipient, remove that recipient, then delete the list —
    /// asserting the delete confirm dialog, the success toast, and the row disappearing.
    /// </summary>
    [SkippableFact]
    public async Task Create_AddRecipient_RemoveRecipient_Delete_RoundTrips()
    {
        Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);

        var name = CliRunner.UniqueName("notiflist");
        const string recipEmail = "zzrec@example.invalid";

        var page = await _pw.NewAuthenticatedPageAsync();

        try
        {
            // ── CREATE ────────────────────────────────────────────────────────────────
            await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{ListsUrl}/create");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            await Assertions.Expect(page.Locator("h4:has-text('Add Notification List')")).ToBeVisibleAsync();

            // The create form has exactly one input (the list name); recipients are edit-only.
            await page.Locator("input.form-control").First.FillAsync(name);
            await page.Locator("button.btn-success:has-text('Save')").ClickAsync();

            // On success the form redirects back to the list page (no toast).
            await PlaywrightFixture.WaitForPathAsync(page, ListsUrl, excludePath: "/create");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            var listRow = page.Locator("tr").Filter(new() { HasText = name });
            await Assertions.Expect(listRow).ToBeVisibleAsync();
            // Make the row locator strict-mode-safe: assert exactly one match before acting.
            await Assertions.Expect(listRow).ToHaveCountAsync(1, new() { Timeout = 10_000 });

            // ── ADD RECIPIENT ─────────────────────────────────────────────────────────
            await listRow.Locator("button.btn-outline-primary.btn-sm:has-text('Edit')").ClickAsync();

            // The Edit click triggers Blazor enhanced navigation (a SignalR round-trip that
            // loads the edit form's data); wait for it to settle before asserting the heading.
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            await Assertions.Expect(page.Locator("h4:has-text('Edit Notification List')")).ToBeVisibleAsync();

            // The edit page has TWO "Name" text inputs (list name + recipient name). Scope the
            // recipient form to the card that contains the email input to disambiguate.
            var recipientCard = page.Locator(".card").Filter(new() { Has = page.Locator("input[type=email]") });
            await recipientCard.Locator("input[type=text].form-control").FillAsync("zzrec");
            await recipientCard.Locator("input[type=email]").FillAsync(recipEmail);
            await recipientCard.Locator("button.btn-success:has-text('Add')").ClickAsync();

            var recipientRow = page.Locator("tr").Filter(new() { HasText = recipEmail });
            await Assertions.Expect(recipientRow).ToBeVisibleAsync();

            // ── REMOVE RECIPIENT ──────────────────────────────────────────────────────
            // Direct delete — no confirm dialog.
            await recipientRow.Locator("button.btn-outline-danger.btn-sm:has-text('Delete')").ClickAsync();
            await Assertions.Expect(page.Locator("tr").Filter(new() { HasText = recipEmail }))
                .ToHaveCountAsync(0, new() { Timeout = 10_000 });

            // ── DELETE LIST ───────────────────────────────────────────────────────────
            await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{ListsUrl}");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            var listRowAgain = page.Locator("tr").Filter(new() { HasText = name });
            // Make the row locator strict-mode-safe: assert exactly one match before acting.
            await Assertions.Expect(listRowAgain).ToHaveCountAsync(1, new() { Timeout = 10_000 });
            await listRowAgain.Locator("button.btn-outline-danger.btn-sm:has-text('Delete')").ClickAsync();

            // Confirm the global danger dialog.
            await Assertions.Expect(page.Locator(".modal-footer .btn-danger")).ToBeVisibleAsync();
            await page.Locator(".modal-footer .btn-danger").ClickAsync();

            // Assert the success toast in one web-first check — count + body text together — so the
            // second assertion can't race the toast's 5s auto-dismiss.
            await Assertions.Expect(page.Locator(".toast", new() { HasText = "Deleted." }))
                .ToHaveCountAsync(1, new() { Timeout = 15_000 });
            await Assertions.Expect(page.Locator("tr").Filter(new() { HasText = name }))
                .ToHaveCountAsync(0, new() { Timeout = 10_000 });
        }
        finally
        {
            foreach (var id in await CliRunner.ListNotificationListIdsByNamePrefixAsync(name))
                await CliRunner.DeleteNotificationListAsync(id);
        }
    }

    /// <summary>
    /// Submits the create form with a blank name and asserts the inline 'Name required.'
    /// validation error appears and the page does not navigate away. Mutates nothing.
    /// </summary>
    [SkippableFact]
    public async Task CreateForm_EmptyName_ShowsInlineError()
    {
        Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);

        var page = await _pw.NewAuthenticatedPageAsync();
        await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{ListsUrl}/create");
        await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

        await page.Locator("button.btn-success:has-text('Save')").ClickAsync();

        await Assertions.Expect(page.Locator("div.text-danger.small:has-text('Name required.')")).ToBeVisibleAsync();
        await Assertions.Expect(page).ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/create"));
    }
}
