using ClaudeDo.Ui.Services;

namespace ClaudeDo.Ui.Tests;

public sealed class RepoScannerTests : IDisposable
{
    private readonly string _root =
        Path.Combine(Path.GetTempPath(), "repo-scan-" + Guid.NewGuid().ToString("N"));

    public RepoScannerTests() => Directory.CreateDirectory(_root);

    public void Dispose()
    {
        try { Directory.Delete(_root, recursive: true); } catch { }
    }

    private string MakeDir(string name)
    {
        var p = Path.Combine(_root, name);
        Directory.CreateDirectory(p);
        return p;
    }

    [Fact]
    public void Scan_ReturnsSubfoldersWithGitDirectory()
    {
        var repo = MakeDir("repo-a");
        Directory.CreateDirectory(Path.Combine(repo, ".git"));

        var result = RepoScanner.Scan(_root);

        Assert.Single(result);
        Assert.Equal("repo-a", result[0].Name);
        Assert.Equal(repo, result[0].FullPath);
    }

    [Fact]
    public void Scan_TreatsDotGitFileAsRepo()
    {
        var repo = MakeDir("worktree-repo");
        File.WriteAllText(Path.Combine(repo, ".git"), "gitdir: ../somewhere");

        var result = RepoScanner.Scan(_root);

        Assert.Single(result);
        Assert.Equal("worktree-repo", result[0].Name);
    }

    [Fact]
    public void Scan_IgnoresPlainFolders()
    {
        MakeDir("not-a-repo");

        var result = RepoScanner.Scan(_root);

        Assert.Empty(result);
    }

    [Fact]
    public void Scan_FindsRepoAtDepthTwo()
    {
        var nested = MakeDir(Path.Combine("outer", "inner"));
        Directory.CreateDirectory(Path.Combine(nested, ".git"));
        // outer itself has no .git

        var result = RepoScanner.Scan(_root);

        Assert.Single(result);
        Assert.Equal("inner", result[0].Name);
        Assert.Equal(nested, result[0].FullPath);
    }

    [Fact]
    public void Scan_FindsRepoAtDepthThree()
    {
        var nested = MakeDir(Path.Combine("customers", "projX", "repo"));
        Directory.CreateDirectory(Path.Combine(nested, ".git"));

        var result = RepoScanner.Scan(_root);

        Assert.Single(result);
        Assert.Equal("repo", result[0].Name);
        Assert.Equal(nested, result[0].FullPath);
    }

    [Fact]
    public void Scan_DoesNotDescendBelowAFoundRepo()
    {
        var repo = MakeDir("repo-a");
        Directory.CreateDirectory(Path.Combine(repo, ".git"));
        var submodule = Path.Combine(repo, "vendor", "nested-repo");
        Directory.CreateDirectory(submodule);
        Directory.CreateDirectory(Path.Combine(submodule, ".git"));

        var result = RepoScanner.Scan(_root);

        Assert.Single(result);
        Assert.Equal("repo-a", result[0].Name);
    }

    [Fact]
    public void Scan_SkipsNodeModules()
    {
        var nodeModules = MakeDir(Path.Combine("node_modules", "some-package"));
        Directory.CreateDirectory(Path.Combine(nodeModules, ".git"));

        var result = RepoScanner.Scan(_root);

        Assert.Empty(result);
    }

    [Fact]
    public void Scan_ReturnsEmptyForMissingFolder()
    {
        var result = RepoScanner.Scan(Path.Combine(_root, "does-not-exist"));

        Assert.Empty(result);
    }

    [Fact]
    public void Scan_ReturnsEmptyWhenRootIsAReparsePoint()
    {
        var repo = MakeDir("real-repo");
        Directory.CreateDirectory(Path.Combine(repo, ".git"));

        var junction = Path.Combine(_root, "junction-to-real-repo");
        var mklink = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
        {
            FileName = "cmd.exe",
            Arguments = $"/c mklink /J \"{junction}\" \"{repo}\"",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        })!;
        mklink.WaitForExit();
        Assert.True(Directory.Exists(junction), "Failed to create test junction: " + mklink.StandardError.ReadToEnd());

        var result = RepoScanner.Scan(junction);

        Assert.Empty(result);
    }

    [Fact]
#pragma warning disable CA1416 // ACL manipulation is Windows-only; this test targets the Windows dev/CI environment
    public void Scan_ContinuesAfterUnreadableSubdirectory()
    {
        var repo = MakeDir("repo-a");
        Directory.CreateDirectory(Path.Combine(repo, ".git"));
        var locked = new DirectoryInfo(MakeDir("locked"));
        var denyRule = new System.Security.AccessControl.FileSystemAccessRule(
            Environment.UserName,
            System.Security.AccessControl.FileSystemRights.ListDirectory,
            System.Security.AccessControl.AccessControlType.Deny);
        var acl = locked.GetAccessControl();
        acl.AddAccessRule(denyRule);
        try
        {
            locked.SetAccessControl(acl);

            var result = RepoScanner.Scan(_root);

            Assert.Single(result);
            Assert.Equal("repo-a", result[0].Name);
        }
        finally
        {
            acl.RemoveAccessRule(denyRule);
            locked.SetAccessControl(acl);
        }
    }
#pragma warning restore CA1416
}
