using ClaudeDo.Installer.Checks;
using ClaudeDo.Installer.Core;

namespace ClaudeDo.Installer.Tests;

public sealed class EnvironmentCheckServiceTests
{
    private sealed class FakeCheck : IEnvironmentCheck
    {
        private readonly Func<InstallContext, CancellationToken, Task<CheckResult>>? _run;

        public FakeCheck(string id, CheckSeverity severity, Func<InstallContext, CancellationToken, Task<CheckResult>>? run = null)
        {
            Id = id;
            Severity = severity;
            _run = run;
        }

        public string Id { get; }
        public CheckSeverity Severity { get; }

        public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) =>
            _run is null
                ? Task.FromResult(CheckResult.Ok(Id, Severity, "title", "ok"))
                : _run(ctx, ct);
    }

    private sealed class ThrowingCheck : IEnvironmentCheck
    {
        public string Id => "throwing";
        public CheckSeverity Severity => CheckSeverity.Error;

        public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) =>
            throw new InvalidOperationException("boom");
    }

    [Fact]
    public async Task RunAllAsync_PreservesInputOrder_RegardlessOfCompletionOrder()
    {
        var slow = new FakeCheck("slow", CheckSeverity.Warning, async (_, ct) =>
        {
            await Task.Delay(50, ct);
            return CheckResult.Ok("slow", CheckSeverity.Warning, "title", "ok");
        });
        var fast = new FakeCheck("fast", CheckSeverity.Warning);
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { slow, fast });

        var report = await service.RunAllAsync(new InstallContext(), CancellationToken.None);

        Assert.Equal(new[] { "slow", "fast" }, report.Results.Select(r => r.Id));
    }

    [Fact]
    public async Task HasBlockingError_True_WhenErrorAndFailed()
    {
        var check = new FakeCheck("id", CheckSeverity.Error, (_, _) =>
            Task.FromResult(CheckResult.Fail("id", CheckSeverity.Error, "title", "bad")));
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { check });

        var report = await service.RunAllAsync(new InstallContext(), CancellationToken.None);

        Assert.True(report.HasBlockingError);
    }

    [Fact]
    public async Task HasBlockingError_False_WhenWarningAndFailed()
    {
        var check = new FakeCheck("id", CheckSeverity.Warning, (_, _) =>
            Task.FromResult(CheckResult.Fail("id", CheckSeverity.Warning, "title", "bad")));
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { check });

        var report = await service.RunAllAsync(new InstallContext(), CancellationToken.None);

        Assert.False(report.HasBlockingError);
    }

    [Fact]
    public async Task HasBlockingError_False_WhenErrorAndUnknown()
    {
        var check = new FakeCheck("id", CheckSeverity.Error, (_, _) =>
            Task.FromResult(CheckResult.Unknown("id", CheckSeverity.Error, "title", "dunno")));
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { check });

        var report = await service.RunAllAsync(new InstallContext(), CancellationToken.None);

        Assert.False(report.HasBlockingError);
    }

    [Fact]
    public async Task RunAllAsync_ThrowingCheck_BecomesUnknown_OthersStillRun_ServiceDoesNotThrow()
    {
        var throwing = new ThrowingCheck();
        var ok = new FakeCheck("ok", CheckSeverity.Warning);
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { throwing, ok });

        var report = await service.RunAllAsync(new InstallContext(), CancellationToken.None);

        var throwingResult = report.Results[0];
        Assert.Equal(CheckStatus.Unknown, throwingResult.Status);
        Assert.Contains("boom", throwingResult.Detail);

        var okResult = report.Results[1];
        Assert.Equal(CheckStatus.Ok, okResult.Status);
    }

    [Fact]
    public async Task RunAllAsync_PassesCancellationToken_ToChecks()
    {
        CancellationToken? seen = null;
        var check = new FakeCheck("id", CheckSeverity.Warning, (_, ct) =>
        {
            seen = ct;
            return Task.FromResult(CheckResult.Ok("id", CheckSeverity.Warning, "title", "ok"));
        });
        var service = new EnvironmentCheckService(new IEnvironmentCheck[] { check });
        using var cts = new CancellationTokenSource();

        await service.RunAllAsync(new InstallContext(), cts.Token);

        Assert.Equal(cts.Token, seen);
    }
}
