using ClaudeDo.Data.Git;
using ClaudeDo.Worker.Git;
using ClaudeDo.Worker.Tests.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;

namespace ClaudeDo.Worker.Tests.Git;

public sealed class BaseDirtyCheckerTests : IDisposable
{
    private readonly List<GitRepoFixture> _repos = new();

    private static bool GitAvailable => GitRepoFixture.IsGitAvailable();

    public void Dispose()
    {
        foreach (var r in _repos) r.Dispose();
    }

    private GitRepoFixture CreateRepo()
    {
        var repo = new GitRepoFixture();
        _repos.Add(repo);
        return repo;
    }

    private static BaseDirtyChecker CreateSut() =>
        new(new GitService(), NullLogger<BaseDirtyChecker>.Instance);

    [Fact]
    public async Task CheckAsync_NullWorkingDir_ReturnsNull()
    {
        var sut = CreateSut();
        Assert.Null(await sut.CheckAsync(null, default));
        Assert.Null(await sut.CheckAsync("", default));
        Assert.Null(await sut.CheckAsync("   ", default));
    }

    [Fact]
    public async Task CheckAsync_NotAGitRepo_ReturnsNull()
    {
        var dir = Path.Combine(Path.GetTempPath(), $"claudedo_notgit_{Guid.NewGuid():N}");
        Directory.CreateDirectory(dir);
        try
        {
            var sut = CreateSut();
            Assert.Null(await sut.CheckAsync(dir, default));
        }
        finally
        {
            Directory.Delete(dir, true);
        }
    }

    [Fact]
    public async Task CheckAsync_CleanRepo_ReturnsNull()
    {
        if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }

        var repo = CreateRepo();
        var sut = CreateSut();

        Assert.Null(await sut.CheckAsync(repo.RepoDir, default));
    }

    [Fact]
    public async Task CheckAsync_UntrackedFile_ReturnsUntrackedCountOnly()
    {
        if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }

        var repo = CreateRepo();
        File.WriteAllText(Path.Combine(repo.RepoDir, "scratch.txt"), "new");
        var sut = CreateSut();

        var warning = await sut.CheckAsync(repo.RepoDir, default);

        Assert.NotNull(warning);
        Assert.Equal(0, warning!.ModifiedCount);
        Assert.Equal(1, warning.UntrackedCount);
    }

    [Fact]
    public async Task CheckAsync_ModifiedTrackedFile_ReturnsModifiedCountOnly()
    {
        if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }

        var repo = CreateRepo();
        File.WriteAllText(Path.Combine(repo.RepoDir, "README.md"), "edited");
        var sut = CreateSut();

        var warning = await sut.CheckAsync(repo.RepoDir, default);

        Assert.NotNull(warning);
        Assert.Equal(1, warning!.ModifiedCount);
        Assert.Equal(0, warning.UntrackedCount);
    }

    [Fact]
    public async Task CheckAsync_ModifiedAndUntracked_ReportsBothSeparately()
    {
        if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }

        var repo = CreateRepo();
        File.WriteAllText(Path.Combine(repo.RepoDir, "README.md"), "edited");
        File.WriteAllText(Path.Combine(repo.RepoDir, "scratch.txt"), "new");
        var sut = CreateSut();

        var warning = await sut.CheckAsync(repo.RepoDir, default);

        Assert.NotNull(warning);
        Assert.Equal(1, warning!.ModifiedCount);
        Assert.Equal(1, warning.UntrackedCount);
    }

    // Proves the TTL cache collapses repeated checks against the same working dir into a
    // single underlying `git status` call -- the acceptance requirement that a batch queue
    // op over many tasks in one list must not shell out once per task. A second check
    // immediately after a repo mutation still returns the first (stale) answer instead of
    // re-running git.
    [Fact]
    public async Task CheckAsync_RepeatedCallsWithinTtl_ReuseCachedResult()
    {
        if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }

        var repo = CreateRepo();
        var sut = CreateSut();

        var first = await sut.CheckAsync(repo.RepoDir, default);
        Assert.Null(first);

        File.WriteAllText(Path.Combine(repo.RepoDir, "scratch.txt"), "new");
        var second = await sut.CheckAsync(repo.RepoDir, default);

        Assert.Null(second);
    }
}
