using System.Diagnostics;
using System.Text;
using ClaudeDo.Data.Git;

namespace ClaudeDo.Worker.Skills;

public sealed class GitRepoCloner : IRepoCloner
{
    private readonly GitService _git;

    public GitRepoCloner(GitService git) => _git = git;

    public async Task<ClonedRepo> CloneAsync(string url, string destDir, CancellationToken ct)
    {
        var parentDir = Path.GetDirectoryName(destDir);
        if (!string.IsNullOrEmpty(parentDir))
            Directory.CreateDirectory(parentDir);

        var (exitCode, stderr) = await RunGitAsync(["clone", "--depth", "1", url, destDir], ct);
        if (exitCode != 0)
            throw new InvalidOperationException($"git clone '{url}' failed (exit {exitCode}): {stderr}");

        var pinnedRef = await _git.RevParseHeadAsync(destDir, ct);
        return new ClonedRepo(destDir, pinnedRef);
    }

    private static async Task<(int ExitCode, string Stderr)> RunGitAsync(IEnumerable<string> args, CancellationToken ct)
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8,
            StandardErrorEncoding = Encoding.UTF8,
        };
        foreach (var a in args) psi.ArgumentList.Add(a);

        using var proc = new Process { StartInfo = psi };
        proc.Start();

        await using var ctr = ct.Register(() =>
        {
            try { proc.Kill(entireProcessTree: true); }
            catch { /* already exited */ }
        });

        var stdoutTask = proc.StandardOutput.ReadToEndAsync();
        var stderrTask = proc.StandardError.ReadToEndAsync();

        await proc.WaitForExitAsync(CancellationToken.None);
        await stdoutTask;
        var stderr = await stderrTask;

        ct.ThrowIfCancellationRequested();

        return (proc.ExitCode, stderr.TrimEnd());
    }
}
