using System.Globalization;
using Microsoft.Playwright;
using Tests.E2e.Infra;
using Tests.E2e.PageObjectModels;
using Xunit.Abstractions;

namespace Tests.E2e;

public class AccessPointCardTests(
    PlaywrightFixture fixture,
    ITestOutputHelper output) : E2ETestBase(fixture, output) {
    private readonly PlaywrightFixture _fixture = fixture;
    private readonly ITestOutputHelper _output = output;

    [Fact]
    public async Task User_Can_Edit_Model_And_Speed_And_Save() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            var newModel = "AP-Model-9000";
            var newSpeed = 2.5;

            await card.BeginEditAsync(name);
            await card.SetModelAsync(name, newModel);
            await card.SetSpeedAsync(name, newSpeed);
            await card.SaveAsync(name);

            await page.ReloadAsync();

            await Assertions.Expect(card.ModelValue(name)).ToHaveTextAsync(newModel);
            await Assertions.Expect(card.SpeedValue(name))
                .ToHaveTextAsync($"{newSpeed.ToString(CultureInfo.InvariantCulture)} Gbps");

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Cancel_Edit_And_Changes_Are_Not_Applied() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            // Capture current values (may be empty depending on seed data)
            var beforeModel = await card.ModelSection(name).TextContentAsync();
            var beforeSpeed = await card.SpeedSection(name).TextContentAsync();

            await card.BeginEditAsync(name);
            await card.SetModelAsync(name, "SHOULD-NOT-SAVE");
            await card.SetSpeedAsync(name, 9.9);
            await card.CancelEditAsync(name);

            var afterModel = await card.ModelSection(name).TextContentAsync();
            var afterSpeed = await card.SpeedSection(name).TextContentAsync();

            await Assertions.Expect(card.ModelSection(name)).ToHaveTextAsync(beforeModel ?? "");
            await Assertions.Expect(card.SpeedSection(name)).ToHaveTextAsync(beforeSpeed ?? "");

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Rename_AccessPoint_From_Card() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];
        var newName = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            await card.RenameAsync(name, newName);

            // After rename, the card test id uses the new name
            await card.AssertCardVisibleAsync(newName);

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Clone_AccessPoint_From_Card() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];
        var cloneName = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            await card.CloneAsync(name, cloneName);

            // Clone navigates to the clone details page
            await card.AssertCardVisibleAsync(cloneName);

            // Cleanup: delete clone then original (both from details pages)
            await card.DeleteAsync(cloneName);
            await page.WaitForURLAsync("**/hardware/tree");

            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();
            await list.OpenAccessPointAsync(name);

            await card.AssertCardVisibleAsync(name);
            await card.DeleteAsync(name);

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Delete_AccessPoint_From_Card_And_Is_Redirected() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            await card.DeleteAsync(name);
            await page.WaitForURLAsync("**/hardware/tree");

            // Verify it’s gone from the list
            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();
            await list.AssertAccessPointDoesNotExist(name);

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Add_And_Remove_Tags_From_AccessPoint_Card() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();
        var name = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(name);
            await page.WaitForURLAsync($"**/resources/hardware/{name}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(name);

            TagsPom tags = card.Tags;

            // -------------------------------------------------
            // Add multiple tags in one modal interaction
            // -------------------------------------------------

            await tags.AddTagsAsync("accesspoint", "Foo", "Bar", "Baz");

            await tags.AssertTagVisibleAsync("accesspoint", "Foo");
            await tags.AssertTagVisibleAsync("accesspoint", "Bar");
            await tags.AssertTagVisibleAsync("accesspoint", "Baz");

            // -------------------------------------------------
            // Remove a single tag
            // -------------------------------------------------

            await tags.RemoveTagAsync("accesspoint", "Bar");

            await tags.AssertTagNotVisibleAsync("accesspoint", "Bar");
            await tags.AssertTagVisibleAsync("accesspoint", "Foo");
            await tags.AssertTagVisibleAsync("accesspoint", "Baz");

            // -------------------------------------------------
            // Reload to verify persistence
            // -------------------------------------------------

            await page.ReloadAsync();

            await tags.AssertTagVisibleAsync("accesspoint", "Foo");
            await tags.AssertTagVisibleAsync("accesspoint", "Baz");
            await tags.AssertTagNotVisibleAsync("accesspoint", "Bar");

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task User_Can_Add_Ports_To_Two_AccessPoints_And_Connect_Them() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();

        var ap1 = $"e2e-ap-{Guid.NewGuid():N}"[..16];
        var ap2 = $"e2e-ap-{Guid.NewGuid():N}"[..16];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            // -------------------------------------------------
            // Create first AP
            // -------------------------------------------------

            await list.AddAccessPointAsync(ap1);
            await page.WaitForURLAsync($"**/resources/hardware/{ap1}");

            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(ap1);

            // Add port group to AP1
            await card.AddPortGroupAsync(
                "rj45",
                "1",
                2);

            // -------------------------------------------------
            // Create second AP
            // -------------------------------------------------

            await layout.GotoHardwareAsync();
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();

            await list.AddAccessPointAsync(ap2);
            await page.WaitForURLAsync($"**/resources/hardware/{ap2}");

            await card.AssertCardVisibleAsync(ap2);

            // Add port group to AP2
            await card.AddPortGroupAsync(
                "sfp+",
                "2.5",
                2);
            // -------------------------------------------------
            // Go back to AP1 to create connection
            // -------------------------------------------------

            await layout.GotoHardwareAsync();
            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();
            await list.OpenAccessPointAsync(ap1);

            await card.AssertCardVisibleAsync(ap1);

            // -------------------------------------------------
            // Open connection modal from port
            // -------------------------------------------------

            await card.OpenConnectionFromPortAsync(0, 0);

            // -------------------------------------------------
            // Create connection
            // -------------------------------------------------

            await card.CreateConnectionAsync(
                ap1,
                "rj45 — 1 Gbps (2)", // example label — adjust if needed
                "Port 1",
                ap2,
                "sfp+ — 2.5 Gbps (2)",
                "Port 1");

            // -------------------------------------------------
            // Verify connection indicator appears
            // -------------------------------------------------

            await card.Ports.AssertPortVisibleAsync("accesspoint-ports", 0, 0);

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }

    [Fact]
    public async Task Connection_Modal_Stores_And_Replays_The_Label() {
        (IBrowserContext context, IPage page) = await CreatePageAsync();

        var ap1 = $"e2e-ap-{Guid.NewGuid():N}"[..16];
        var ap2 = $"e2e-ap-{Guid.NewGuid():N}"[..16];
        var label = $"link-{Guid.NewGuid():N}"[..12];

        try {
            await page.GotoAsync(_fixture.BaseUrl);

            var layout = new MainLayoutPom(page);
            await layout.AssertLoadedAsync();
            await layout.GotoHardwareAsync();

            var hardwareTree = new HardwareTreePom(page);
            await hardwareTree.AssertLoadedAsync();
            await hardwareTree.GotoAccessPointsListAsync();

            var list = new AccessPointsListPom(page);
            await list.AssertLoadedAsync();

            // Two APs each with a port group
            await list.AddAccessPointAsync(ap1);
            await page.WaitForURLAsync($"**/resources/hardware/{ap1}");
            var card = new AccessPointCardPom(page);
            await card.AssertCardVisibleAsync(ap1);
            await card.AddPortGroupAsync("rj45", "1", 2);

            await layout.GotoHardwareAsync();
            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();
            await list.AddAccessPointAsync(ap2);
            await page.WaitForURLAsync($"**/resources/hardware/{ap2}");
            await card.AssertCardVisibleAsync(ap2);
            await card.AddPortGroupAsync("sfp+", "2.5", 2);

            // Open AP1 and create a connection with a label
            await layout.GotoHardwareAsync();
            await hardwareTree.GotoAccessPointsListAsync();
            await list.AssertLoadedAsync();
            await list.OpenAccessPointAsync(ap1);
            await card.AssertCardVisibleAsync(ap1);

            await card.OpenConnectionFromPortAsync(0, 0);
            await card.CreateConnectionAsync(
                ap1, "rj45 — 1 Gbps (2)", "Port 1",
                ap2, "sfp+ — 2.5 Gbps (2)", "Port 1",
                label: label);

            // Re-open the modal on the same port — the stored label should
            // pre-populate the input, proving it round-tripped to the YAML
            // and back through `GetConnectionForPortAsync`.
            await card.OpenConnectionFromPortAsync(0, 0);

            await Assertions.Expect(card.ConnectionLabelInput()).ToHaveValueAsync(label);

            await context.CloseAsync();
        }
        finally {
            await context.CloseAsync();
        }
    }
}
