using System.Diagnostics;

namespace Sdt.Runner;

public sealed record RunResult(int ExitCode, TimeSpan Elapsed)
{
    public bool Success => ExitCode == 0;
}

public static class ProcessRunner
{
    /// <summary>
    /// Runs a command with the given args, streaming stdout/stderr via <paramref name="onOutput"/>.
    /// onOutput receives (line, isStderr).
    /// </summary>
    public static async Task<RunResult> RunAsync(
        string command,
        IEnumerable<string> args,
        string workingDir,
        Action<string, bool> onOutput,
        IReadOnlyDictionary<string, string>? envOverrides = null,
        CancellationToken cancellationToken = default)
    {
        var psi = new ProcessStartInfo
        {
            FileName = Core.CommandResolver.Resolve(command),
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            WorkingDirectory = workingDir,
        };

        if (envOverrides is not null)
        {
            foreach (var kvp in envOverrides)
                psi.Environment[kvp.Key] = kvp.Value;
        }

        foreach (var arg in args)
            psi.ArgumentList.Add(arg);

        var sw = Stopwatch.StartNew();

        using var process = new Process { StartInfo = psi };

        void OnCancel(object? sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = true; // Prevent SDT from exiting immediately
            try { process.Kill(entireProcessTree: true); } catch { }
        }

        Console.CancelKeyPress += OnCancel;

        try
        {
            process.Start();

            var stdoutTask = DrainAsync(process.StandardOutput, line => onOutput(line, false), cancellationToken);
            var stderrTask = DrainAsync(process.StandardError, line => onOutput(line, true), cancellationToken);

            await Task.WhenAll(stdoutTask, stderrTask).ConfigureAwait(false);
            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            Console.CancelKeyPress -= OnCancel;
        }

        sw.Stop();
        return new RunResult(process.ExitCode, sw.Elapsed);
    }

    private static async Task DrainAsync(StreamReader reader, Action<string> emit, CancellationToken ct)
    {
        string? line;
        while ((line = await reader.ReadLineAsync(ct).ConfigureAwait(false)) is not null
               && !ct.IsCancellationRequested)
        {
            emit(line);
        }
    }
}
