using System.Diagnostics;

namespace Flo.Installer.Provisioning;

internal sealed class ProcessRunner : IAsyncDisposable
{
    private readonly Process _process;
    private readonly string _name;

    private ProcessRunner(Process process, string name)
    {
        _process = process;
        _name = name;
    }

    public static ProcessRunner Start(
        string executable,
        IReadOnlyList<string> args,
        IReadOnlyDictionary<string, string>? env,
        string name)
    {
        var psi = new ProcessStartInfo(executable)
        {
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
        };
        foreach (var a in args) psi.ArgumentList.Add(a);
        if (env is not null)
            foreach (var kv in env) psi.Environment[kv.Key] = kv.Value;

        var proc = Process.Start(psi) ?? throw new InvalidOperationException($"Failed to start {executable}");

        proc.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine($"[{name}] {e.Data}"); };
        proc.ErrorDataReceived  += (_, e) => { if (e.Data != null) Console.Error.WriteLine($"[{name}] {e.Data}"); };
        proc.BeginOutputReadLine();
        proc.BeginErrorReadLine();

        return new ProcessRunner(proc, name);
    }

    public bool HasExited => _process.HasExited;
    public int ExitCode => _process.ExitCode;
    public Process Process => _process;

    public async Task StopAsync(TimeSpan timeout)
    {
        if (_process.HasExited) return;
        _process.Kill(entireProcessTree: true);
        await _process.WaitForExitAsync().WaitAsync(timeout);
    }

    public async ValueTask DisposeAsync()
    {
        if (!_process.HasExited)
        {
            try { await StopAsync(TimeSpan.FromSeconds(10)); }
            catch (Exception ex) { Console.Error.WriteLine($"[{_name}] stop error: {ex.Message}"); }
        }
        _process.Dispose();
    }
}
