using System.Diagnostics;

namespace ClaudeDo.Worker.Tests.Infrastructure;

public sealed class GitRepoFixture : IDisposable
{
    public string RepoDir { get; }
    public string BaseCommit { get; }

    public GitRepoFixture()
    {
        RepoDir = Path.Combine(Path.GetTempPath(), $"claudedo_gittest_{Guid.NewGuid():N}");
        Directory.CreateDirectory(RepoDir);

        RunGit(RepoDir, "init");
        RunGit(RepoDir, "config", "user.name", "test");
        RunGit(RepoDir, "config", "user.email", "test@example.com");

        File.WriteAllText(Path.Combine(RepoDir, "README.md"), "# test repo");
        RunGit(RepoDir, "add", "-A");
        RunGit(RepoDir, "commit", "-m", "initial commit");

        BaseCommit = RunGit(RepoDir, "rev-parse", "HEAD").Trim();
    }

    public void Dispose()
    {
        try
        {
            // Force-remove read-only .git objects on Windows.
            ForceDeleteDirectory(RepoDir);
        }
        catch { /* best effort */ }
    }

    public static bool IsGitAvailable()
    {
        try
        {
            var psi = new ProcessStartInfo("git", "--version")
            {
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var p = Process.Start(psi)!;
            p.WaitForExit(5000);
            return p.ExitCode == 0;
        }
        catch
        {
            return false;
        }
    }

    internal static string RunGit(string workDir, params string[] args)
    {
        var psi = new ProcessStartInfo("git")
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        psi.ArgumentList.Add("-C");
        psi.ArgumentList.Add(workDir);
        foreach (var a in args) psi.ArgumentList.Add(a);

        using var proc = Process.Start(psi)!;
        var stdout = proc.StandardOutput.ReadToEnd();
        var stderr = proc.StandardError.ReadToEnd();
        proc.WaitForExit();

        if (proc.ExitCode != 0)
            throw new InvalidOperationException($"git {string.Join(' ', args)} failed: {stderr}");

        return stdout;
    }

    /// <summary>
    /// Creates a new git repo at <paramref name="dir"/> with a seed commit.
    /// Used by planning tests that need a real git repo as list working directory.
    /// </summary>
    public static void InitRepoWithInitialCommit(string dir)
    {
        Directory.CreateDirectory(dir);
        RunGit(dir, "init", "-b", "main");
        RunGit(dir, "config", "user.email", "test@claudedo.local");
        RunGit(dir, "config", "user.name", "test");
        File.WriteAllText(Path.Combine(dir, "README.md"), "seed\n");
        RunGit(dir, "add", "-A");
        RunGit(dir, "commit", "-m", "chore: seed");
    }

    private static void ForceDeleteDirectory(string path)
    {
        if (!Directory.Exists(path)) return;
        foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
        {
            File.SetAttributes(file, FileAttributes.Normal);
        }
        Directory.Delete(path, true);
    }
}
