using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;

/// <summary>
/// Characterization tests for CentralUI-015 (re-triaged Won't Fix — see
/// findings.md). The finding claimed <c>ContinueWith(..., TaskScheduler.Default)</c>
/// made callers resume off the render thread; that premise is incorrect — an
/// <c>await</c> always resumes on the awaiter's own captured
/// <see cref="SynchronizationContext"/> regardless of where the awaited task
/// completes. <c>ConfirmAsync_AwaiterResumesOnItsCapturedSyncContext</c> pins
/// that correct behaviour (it passes against both the old <c>ContinueWith</c>
/// form and the current inline-projection form). The remaining tests pin the
/// dialog result-resolution contract.
/// </summary>
public class DialogServiceThreadingTests
{
    /// <summary>
    /// A single-threaded sync context that records every posted callback —
    /// stands in for the Blazor renderer's dispatcher.
    /// </summary>
    private sealed class TrackingSyncContext : SynchronizationContext
    {
        private readonly Thread _thread;
        private readonly System.Collections.Concurrent.BlockingCollection<(SendOrPostCallback, object?)> _queue = new();
        public int PostedCount;

        public TrackingSyncContext()
        {
            _thread = new Thread(() =>
            {
                SetSynchronizationContext(this);
                foreach (var (cb, st) in _queue.GetConsumingEnumerable())
                {
                    cb(st);
                }
            }) { IsBackground = true };
            _thread.Start();
        }

        public override void Post(SendOrPostCallback d, object? state)
        {
            Interlocked.Increment(ref PostedCount);
            _queue.Add((d, state));
        }

        public void Complete() => _queue.CompleteAdding();
    }

    [Fact]
    public async Task ConfirmAsync_AwaiterResumesOnItsCapturedSyncContext()
    {
        var service = new DialogService();
        var ctx = new TrackingSyncContext();

        // Run the awaiting "component" code on the tracking context.
        var done = new TaskCompletionSource<int>();
        ctx.Post(async void (_) =>
        {
            try
            {
                var task = service.ConfirmAsync("t", "m");
                // Resolve from another thread, mimicking the host dispatching.
                _ = Task.Run(() => service.Resolve(true));
                await task;
                // The continuation after the await must be back on the tracking
                // context's single thread.
                done.SetResult(Environment.CurrentManagedThreadId);
            }
            catch (Exception ex)
            {
                done.SetException(ex);
            }
        }, null);

        var resumeThreadId = await done.Task;
        ctx.Complete();

        // The continuation was posted to (and ran on) the captured context.
        Assert.True(ctx.PostedCount >= 1,
            "ConfirmAsync continuation must post back to the caller's SynchronizationContext.");
        Assert.NotEqual(Environment.CurrentManagedThreadId, resumeThreadId);
    }

    [Fact]
    public async Task ConfirmAsync_ResolvesWithExpectedValue()
    {
        var service = new DialogService();

        var task = service.ConfirmAsync("t", "m");
        service.Resolve(true);

        Assert.True(await task);
    }

    [Fact]
    public async Task PromptAsync_ResolvesWithExpectedValue()
    {
        var service = new DialogService();

        var task = service.PromptAsync("t", "label");
        service.Resolve("typed value");

        Assert.Equal("typed value", await task);
    }

    [Fact]
    public async Task PromptAsync_CancelledResolvesToNull()
    {
        var service = new DialogService();

        var task = service.PromptAsync("t", "label");
        service.Resolve(null);

        Assert.Null(await task);
    }
}
