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

namespace DragonISO.App.Tests.Integration;

/// <summary>
/// Shared WPF Application + STA dispatcher fixture. Created once for
/// every integration test class that asks for it; all test methods
/// post their work to the fixture's dispatcher via <see cref="Run"/>.
///
/// Rationale: <see cref="Application"/> is one-per-AppDomain. Tests
/// that each instantiate their own (or use Xunit.StaFact's per-test
/// STA) collide on the second call ("Cannot create more than one
/// Application instance in the same AppDomain"). A long-lived
/// fixture creates exactly one Application on a dedicated STA thread
/// and reuses its dispatcher for the lifetime of the test class.
/// </summary>
public sealed class WpfHostFixture : IDisposable
{
    private readonly Thread _uiThread;
    private readonly ManualResetEventSlim _ready = new(false);
    private Dispatcher? _dispatcher;
    private Application? _application;
    private Exception? _initFailure;

    public WpfHostFixture()
    {
        _uiThread = new Thread(() =>
        {
            try
            {
                // Application is process-singleton; only construct if the
                // current AppDomain hasn't already minted one (e.g. another
                // fixture in the same run).
                _application = Application.Current ?? new Application();
                _dispatcher = Dispatcher.CurrentDispatcher;
                _ready.Set();
                Dispatcher.Run();
            }
            catch (Exception ex)
            {
                _initFailure = ex;
                _ready.Set();
            }
        });
        _uiThread.SetApartmentState(ApartmentState.STA);
        _uiThread.IsBackground = true;
        _uiThread.Start();
        _ready.Wait();
        if (_initFailure is not null)
            throw new InvalidOperationException("WPF host thread failed to initialise.", _initFailure);
    }

    public Application Application => _application!;
    public Dispatcher Dispatcher => _dispatcher!;

    /// <summary>
    /// Marshal <paramref name="work"/> onto the fixture's STA dispatcher
    /// and await its completion. Exceptions inside <paramref name="work"/>
    /// surface back to the caller intact.
    /// </summary>
    public Task<T> Run<T>(Func<T> work) =>
        _dispatcher!.InvokeAsync(work).Task;

    public Task Run(Action work) =>
        _dispatcher!.InvokeAsync(work).Task;

    public void Dispose()
    {
        try { _dispatcher?.InvokeShutdown(); } catch { /* defensive */ }
        try { _uiThread.Join(TimeSpan.FromSeconds(2)); } catch { /* defensive */ }
        _ready.Dispose();
    }
}

/// <summary>
/// Marks an integration test class as sharing the single
/// <see cref="WpfHostFixture"/> Application + Dispatcher. xUnit
/// instantiates the fixture once per collection and injects it via
/// constructor.
/// </summary>
[CollectionDefinition(Name)]
public sealed class WpfHostCollection : ICollectionFixture<WpfHostFixture>
{
    public const string Name = "WpfHost (shared Application + Dispatcher)";
}
