using System.Diagnostics;
using System.IO;
using System.Text;

using ClaudeDo.Installer.Core.Interfaces;

namespace ClaudeDo.Installer.Core;

public sealed class ProcessRunner : IProcessRunner
{
    /// The injectable, progress-less shape. Everything else calls the static overload directly.
    public Task<(int ExitCode, string Output)> RunAsync(
        string fileName, string arguments, string? workingDirectory, CancellationToken ct)
        => RunAsync(fileName, arguments, workingDirectory, progress: null, ct);

    public static async Task<(int ExitCode, string Output)> RunAsync(
        string fileName,
        string arguments,
        string? workingDirectory,
        IProgress<string>? progress,
        CancellationToken ct)
    {
        var output = new StringBuilder();
        var outputLock = new object();

        using var process = new Process();
        process.StartInfo = new ProcessStartInfo
        {
            FileName = fileName,
            Arguments = arguments,
            WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory,
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        };

        if (!process.Start())
            return (-1, "Failed to start process");

        var stdoutTask = ReadStreamAsync(process.StandardOutput, output, outputLock, progress);
        var stderrTask = ReadStreamAsync(process.StandardError, output, outputLock, progress);

        using var reg = ct.Register(() =>
        {
            try { process.Kill(entireProcessTree: true); } catch { }
        });

        await Task.WhenAll(stdoutTask, stderrTask);
        await process.WaitForExitAsync(ct);

        return (process.ExitCode, output.ToString());
    }

    private static async Task ReadStreamAsync(
        StreamReader reader,
        StringBuilder output,
        object outputLock,
        IProgress<string>? progress)
    {
        while (await reader.ReadLineAsync() is { } line)
        {
            lock (outputLock) { output.AppendLine(line); }
            progress?.Report(line);
        }
    }
}
