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

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

[Collection("Playwright")]
public class ExternalSystemCrudTests
{
    private readonly PlaywrightFixture _pw;

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

    /// <summary>
    /// Full create → card → delete round-trip for an external system via the Central UI.
    /// Uses CliRunner for best-effort teardown so no zztest-extsys-* definition leaks on
    /// failure (the happy path already deletes via the UI).
    /// </summary>
    [SkippableFact]
    public async Task Create_Delete_RoundTrips()
    {
        Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);

        var name = CliRunner.UniqueName("extsys");

        var page = await _pw.NewAuthenticatedPageAsync();

        try
        {
            // ── CREATE ────────────────────────────────────────────────────────────────
            await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/external-systems/create");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
            await Assertions.Expect(page.Locator("h4:has-text('Add External System')")).ToBeVisibleAsync();

            // ExternalSystemForm.razor has THREE input[type=text].form-control fields
            // (Name, Endpoint URL, and Auth Config JSON), so index-based selection is
            // fragile. Anchor each fill to its own div.mb-3 wrapper via the field label
            // so the selectors survive field reordering and uniquely match one element.
            await page.Locator("div.mb-3:has(label:has-text('Name')) input[type=text].form-control")
                .FillAsync(name);
            await page.Locator("div.mb-3:has(label:has-text('Endpoint URL')) input[type=text].form-control")
                .FillAsync("https://example.invalid/api");

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

            // Save redirects back to the list (no toast).
            await PlaywrightFixture.WaitForPathAsync(page, "/design/external-systems", excludePath: "/create");
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

            // The new card must be present (and unique) before we act on it.
            var card = page.Locator("div.card").Filter(new() { HasText = name });
            await Assertions.Expect(card).ToHaveCountAsync(1, new() { Timeout = 10_000 });

            // ── DELETE ────────────────────────────────────────────────────────────────
            // Scope the kebab + delete item to the card's .dropdown for strict-mode safety.
            var cardDropdown = card.Locator(".dropdown");
            await cardDropdown.Locator("button[aria-label^='More actions']").ClickAsync();

            await cardDropdown.Locator(".dropdown-menu button.dropdown-item.text-danger").ClickAsync();

            // Confirm the delete dialog.
            await Assertions.Expect(page.Locator(".modal-title:has-text('Delete External System')")).ToBeVisibleAsync();
            await page.Locator(".modal-footer .btn-danger").ClickAsync();

            // Single web-first assertion on the success toast — toasts auto-dismiss at 5s,
            // so we do NOT chase .toast-body in a second sequential check.
            await Assertions.Expect(page.Locator(".toast", new() { HasText = "Deleted." }))
                .ToHaveCountAsync(1, new() { Timeout = 15_000 });

            // The card must be gone.
            await Assertions.Expect(card).ToHaveCountAsync(0, new() { Timeout = 10_000 });
        }
        finally
        {
            // Best-effort teardown: the happy path deletes via the UI, so this finds
            // nothing; it only fires if the UI path failed mid-way.
            foreach (var id in await CliRunner.ListExternalSystemIdsByNamePrefixAsync(name))
                await CliRunner.DeleteExternalSystemAsync(id);
        }
    }

    /// <summary>
    /// Saving the create form with both fields blank surfaces the inline validation
    /// error and keeps the user on the create page. Mutates nothing.
    /// </summary>
    [SkippableFact]
    public async Task CreateForm_EmptyFields_ShowsInlineError()
    {
        Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);

        var page = await _pw.NewAuthenticatedPageAsync();

        await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/external-systems/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 and URL required.')"))
            .ToBeVisibleAsync();
        await Assertions.Expect(page)
            .ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/create"));
    }
}
