using ClaudeDo.Worker.Lifecycle;
using ModelContextProtocol;
using Xunit;

namespace ClaudeDo.Worker.Tests.Lifecycle;

// A synchronously-collecting IProgress<T>: Progress<T> marshals through the SynchronizationContext
// captured at construction, which is unreliable to assert on immediately in a test.
file sealed class SyncProgress<T> : IProgress<T>
{
    public readonly List<T> Reports = new();
    public void Report(T value) => Reports.Add(value);
}

public sealed class ProgressReporterTests
{
    [Fact]
    public async Task RunAsync_NoProgressOrOnTick_ReturnsWorkResultWithoutTicking()
    {
        var result = await ProgressReporter.RunAsync(Task.FromResult(42), TimeSpan.FromMilliseconds(10), null, "message");

        Assert.Equal(42, result);
    }

    [Fact]
    public async Task RunAsync_SlowWork_ReportsProgressOnEachTick()
    {
        var progress = new SyncProgress<ProgressNotificationValue>();
        var work = Task.Delay(250).ContinueWith(_ => "done");

        var result = await ProgressReporter.RunAsync(work, TimeSpan.FromMilliseconds(50), progress, "waiting");

        Assert.Equal("done", result);
        Assert.NotEmpty(progress.Reports);
        Assert.All(progress.Reports, r => Assert.Contains("waiting", r.Message));
    }

    [Fact]
    public void ReportItem_ReportsMessageAndProgressTotalForEachItem()
    {
        var progress = new SyncProgress<ProgressNotificationValue>();

        for (var i = 1; i <= 3; i++)
            ProgressReporter.ReportItem(progress, "cleaning worktrees", i, 3);

        Assert.Equal(3, progress.Reports.Count);
        Assert.Equal("cleaning worktrees (1/3)", progress.Reports[0].Message);
        Assert.Equal("cleaning worktrees (2/3)", progress.Reports[1].Message);
        Assert.Equal("cleaning worktrees (3/3)", progress.Reports[2].Message);
        Assert.Equal(3, progress.Reports[2].Total);
    }

    [Fact]
    public void ReportItem_NoProgress_DoesNotThrow()
    {
        ProgressReporter.ReportItem(null, "cleaning worktrees", 1, 3);
    }
}
