using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text.Json;
using System.Windows;
using System.Windows.Media;
using FluentAssertions;
using DragonISO.App.Services;
using DragonISO.App.Tests.Fakes;
using DragonISO.App.ViewModels;
using DragonISO.Engine.Domain;
using Xunit;

namespace DragonISO.App.Tests.Integration;

// End-to-end-ish integration tests that need a live WPF Application +
// STA dispatcher. All three live in one class + share a
// WpfHostFixture so Application is created exactly once for the
// suite (Application is one-per-AppDomain â€” multiple test classes
// trying to construct it independently collide).
//
// Coverage per the punch list:
//   â€¢ App-startup headless smoke â€” construct App's bootstrap layers
//     on STA, verify XAML resource resolution + theme apply + VM
//     wiring + MainWindow construction.
//   â€¢ ControlSurface integration â€” boot the server on an ephemeral
//     port, populate a real view-model, hit /participants, verify
//     the JSON includes the live participant.
//   â€¢ Theme swap â€” Dark â†’ Light dictionary swap, brush key resolves
//     to a different value afterward.
[Collection(WpfHostCollection.Name)]
public sealed class IntegrationTests
{
    private readonly WpfHostFixture _wpf;

    public IntegrationTests(WpfHostFixture wpf) => _wpf = wpf;

    private static int PickFreePort()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        try { return ((IPEndPoint)listener.LocalEndpoint).Port; }
        finally { listener.Stop(); }
    }

    private async Task SeedDarkThemeAsync()
    {
        await _wpf.Run(() =>
        {
            var dicts = _wpf.Application.Resources.MergedDictionaries;
            dicts.Clear();
            dicts.Add(new ResourceDictionary
            {
                Source = new Uri("pack://application:,,,/DragonISO;component/Themes/Theme.Dark.xaml", UriKind.Absolute),
            });
        });
    }

    [Fact]
    public async Task ThemeXaml_DarkAndLight_BothLoadWithDistinctWdCanvas()
    {
        // Verifies the real XAML files load via pack URIs (the
        // production code path) and that the two theme files
        // produce different brushes for the same key. End-to-end
        // exercise of the resource pipeline that doesn't depend on
        // Application.Resources global state â€” both dicts are
        // loaded fresh in this call.
        //
        // We don't test ThemeManager.SwapColorDictionary here
        // because Application.Resources is process-wide and
        // sibling-test mutations make the state observably non-
        // deterministic in xUnit's parallel-collection model;
        // ThemeManagerTests (Services/) cover the swap state
        // machine against stubbed seams. This test guards the
        // distinct-XAML-files claim, which is what would otherwise
        // get refactored out by accident.
        await _wpf.Run(() =>
        {
            var darkDict = new ResourceDictionary
            {
                Source = new Uri(
                    "pack://application:,,,/DragonISO;component/Themes/Theme.Dark.xaml",
                    UriKind.Absolute),
            };
            var lightDict = new ResourceDictionary
            {
                Source = new Uri(
                    "pack://application:,,,/DragonISO;component/Themes/Theme.Light.xaml",
                    UriKind.Absolute),
            };

            var darkCanvas  = ((SolidColorBrush)darkDict ["Wd.Canvas"]).Color;
            var lightCanvas = ((SolidColorBrush)lightDict["Wd.Canvas"]).Color;

            darkCanvas.Should().Be(Color.FromRgb(0x0A, 0x0A, 0x0A),
                "Theme.Dark.xaml's Wd.Canvas is the documented #0A0A0A");
            lightCanvas.Should().Be(Color.FromRgb(0xFA, 0xFA, 0xFB),
                "Theme.Light.xaml's Wd.Canvas is the documented #FAFAFB");
        });
    }

    [Fact]
    public async Task AppStartup_FullChain_Constructs_WithoutThrowing()
    {
        // Headless smoke for the App.OnStartup wiring sequence:
        //   1. Application + theme resources are loaded.
        //   2. ThemeManager.Apply() resolves brush keys end-to-end.
        //   3. MainViewModel constructs against a stub controller.
        //   4. MainWindow ctor resolves DataContext + finds the brushes
        //      its templates reference.
        await SeedDarkThemeAsync();

        await _wpf.Run(() =>
        {
            _wpf.Application.Resources.MergedDictionaries.Add(new ResourceDictionary
            {
                Source = new Uri(
                    "pack://application:,,,/DragonISO;component/Themes/WildDragonTheme.xaml",
                    UriKind.Absolute),
            });
        });

        // Everything DependencyObject-touching has to run on the STA
        // dispatcher (Window / DataContext / TryFindResource all
        // VerifyAccess). Do the assertions inside the Run callback so
        // we never marshal a DependencyObject reference back to the
        // test thread.
        await _wpf.Run(() =>
        {
            var tm = new ThemeManager(
                isSystemDark: () => true,
                loadPreference: () => "Dark",
                savePreference: _ => { },
                subscribeToSystemPreference: false);
            tm.Apply();

            var controller = new StubIsoController();
            var vm = new MainViewModel(controller, _wpf.Dispatcher);
            try
            {
                var window = new MainWindow(vm);
                vm.Settings.Should().NotBeNull("MainViewModel wires GlobalSettingsViewModel");
                vm.AlertBanner.Should().NotBeNull();
                window.DataContext.Should().BeSameAs(vm);
                window.TryFindResource("Wd.Canvas").Should().NotBeNull(
                    "Wd.Canvas is defined in Theme.Dark.xaml and used by MainWindow.xaml");
            }
            finally
            {
                vm.Dispose();
            }
        });
    }

    [Fact]
    public async Task ControlSurface_GetParticipants_ReturnsLiveViewModelState()
    {
        var controller = new StubIsoController();
        var vm = await _wpf.Run(() => new MainViewModel(controller, _wpf.Dispatcher));

        // Publish a participant through the controller observable and
        // wait for the dispatcher to drain the InvokeAsync(Background)
        // marshal that adds Alice to the Participants collection.
        controller.PublishParticipants(new Participant(
            Id: Guid.NewGuid(),
            DisplayName: "Alice",
            CurrentSource: null,
            FirstSeen: DateTimeOffset.UtcNow,
            LastSeen: DateTimeOffset.UtcNow));

        // Drain the queue at ApplicationIdle so the Background-priority
        // add has time to complete before we look.
        await _wpf.Dispatcher.InvokeAsync(() => { },
            System.Windows.Threading.DispatcherPriority.ApplicationIdle).Task;

        var server = new ControlSurfaceServer(controller, () => vm, logger: null);
        var port = PickFreePort();
        server.Start(port);
        await Task.Delay(50);
        using var client = new HttpClient { BaseAddress = new Uri($"http://127.0.0.1:{port}") };

        try
        {
            var res = await client.GetAsync("/participants");
            res.StatusCode.Should().Be(HttpStatusCode.OK);
            var body = await res.Content.ReadAsStringAsync();

            using var doc = JsonDocument.Parse(body);
            var participants = doc.RootElement.GetProperty("participants");
            participants.GetArrayLength().Should().Be(1);
            participants[0].GetProperty("displayName").GetString().Should().Be("Alice");
        }
        finally
        {
            server.Stop();
            await _wpf.Run(() => vm.Dispose());
            await controller.DisposeAsync();
        }
    }
}
