using Microsoft.Extensions.Logging.Abstractions;
using TeamsISO.Engine.Domain;
using TeamsISO.Engine.Pipeline;

namespace TeamsISO.Engine.Tests.Pipeline;

public class IsoPipelineTests
{
    private static ExponentialBackoff FastBackoff() =>
        new(maxAttempts: 5, initial: TimeSpan.FromMilliseconds(1), cap: TimeSpan.FromMilliseconds(10));

    private static Func<TimeSpan, CancellationToken, Task> NoDelay() =>
        (_, _) => Task.CompletedTask;

    [Fact]
    public async Task StartAsync_TransitionsIdleToReceiving()
    {
        var participant = Guid.NewGuid();
        var blocker = new TaskCompletionSource();
        var pipeline = new IsoPipeline(
            participant,
            ct => blocker.Task.WaitAsync(ct),
            FastBackoff(),
            NoDelay(),
            NullLoggerFactory.Instance);

        await pipeline.StartAsync();

        pipeline.State.Should().Be(IsoState.Receiving);

        blocker.SetResult();
        await pipeline.StopAsync();
    }

    [Fact]
    public async Task StopAsync_TransitionsBackToIdle()
    {
        var blocker = new TaskCompletionSource();
        var pipeline = new IsoPipeline(
            Guid.NewGuid(),
            ct => blocker.Task.WaitAsync(ct),
            FastBackoff(),
            NoDelay(),
            NullLoggerFactory.Instance);

        await pipeline.StartAsync();
        await pipeline.StopAsync();

        pipeline.State.Should().Be(IsoState.Idle);
    }

    [Fact]
    public async Task Supervisor_FailsOnce_ThenRecovers_RemainsReceiving()
    {
        int callCount = 0;
        var done = new TaskCompletionSource();
        var pipeline = new IsoPipeline(
            Guid.NewGuid(),
            ct =>
            {
                callCount++;
                if (callCount == 1) throw new InvalidOperationException("boom");
                return done.Task.WaitAsync(ct);
            },
            FastBackoff(),
            NoDelay(),
            NullLoggerFactory.Instance);

        await pipeline.StartAsync();
        await Task.Delay(50);          // let the supervisor cycle through the failure
        callCount.Should().BeGreaterOrEqualTo(2);
        pipeline.ConsecutiveFailures.Should().Be(1);
        pipeline.State.Should().Be(IsoState.Receiving);

        done.SetResult();
        await pipeline.StopAsync();
    }

    [Fact]
    public async Task Supervisor_FailsRepeatedly_TransitionsToError()
    {
        var pipeline = new IsoPipeline(
            Guid.NewGuid(),
            _ => throw new InvalidOperationException("permanent"),
            FastBackoff(),
            NoDelay(),
            NullLoggerFactory.Instance);

        await pipeline.StartAsync();

        // Wait for supervisor to give up (max 5 attempts × ~1ms delay)
        var deadline = DateTime.UtcNow.AddSeconds(2);
        while (pipeline.State != IsoState.Error && DateTime.UtcNow < deadline)
            await Task.Delay(20);

        pipeline.State.Should().Be(IsoState.Error);
        pipeline.ConsecutiveFailures.Should().Be(6); // 5 retries allowed, give up on 6th
    }
}
