using System;
using System.Threading;
using System.Windows.Threading;
using Xunit;

namespace ytLive.Tests;

/// <summary>
/// Tests that spin up the real WPF App need exactly one Application instance per
/// AppDomain (WPF enforces it) — so they share this serial collection and a
/// single App created on one dedicated STA thread. Marshal test bodies onto it
/// via <see cref="RealAppHost.Run"/>, never `new App()` per test.
/// </summary>
[CollectionDefinition("RealApp", DisableParallelization = true)]
public sealed class RealAppCollection : ICollectionFixture<RealAppHost>
{
}

/// <summary>Owns the one-and-only WPF App on a dedicated STA thread.</summary>
public sealed class RealAppHost : IDisposable
{
    private readonly Thread _thread;
    private readonly Dispatcher _dispatcher;

    public RealAppHost()
    {
        Exception? boot = null;
        Dispatcher? dispatcher = null;
        _thread = new Thread(() =>
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                dispatcher = Dispatcher.CurrentDispatcher;
                Dispatcher.Run();
            }
            catch (Exception ex)
            {
                boot = ex;
            }
        });
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();
        while (dispatcher == null && boot == null)
            Thread.Sleep(5);
        if (boot != null)
            throw new Xunit.Sdk.XunitException("Real WPF App failed to start: " + boot);
        _dispatcher = dispatcher!;
    }

    /// <summary>Runs <paramref name="action"/> on the App's STA thread and rethrows any failure.</summary>
    public void Run(Action action)
    {
        Exception? failure = null;
        _dispatcher.Invoke(() =>
        {
            try
            {
                action();
            }
            catch (Exception ex)
            {
                failure = ex;
            }
        });
        if (failure != null)
            throw failure;
    }

    public void Dispose()
    {
        try
        {
            _dispatcher.InvokeShutdown();
        }
        catch
        {
            // The App never ran its message loop — shutdown is best-effort.
        }
        _thread.Join(2000);
    }
}
