using System.Diagnostics;

namespace HanaTui.Hana;

/// <summary>
/// Low-level wrapper for spawning hdbsql and hdbuserstore child processes.
/// All output is streamed line-by-line via callbacks for live log display.
/// </summary>
public sealed class HdbCliRunner
{
    /// <summary>
    /// Lists all hdbuserstore keys. Returns empty list on failure.
    /// </summary>
    public static List<HdbUserstoreKey> ListKeys()
    {
        var result = RunAndCapture(HdbClientLocator.HdbUserstore, "list");
        if (!result.Success)
            return [];

        return HdbUserstoreKey.ParseFrom(result.Output);
    }

    /// <summary>
    /// Tests a key connection by running a trivial SELECT against DUMMY.
    /// Returns true on success.
    /// </summary>
    public static bool TestKey(string key)
    {
        var result = RunAndCapture(
            HdbClientLocator.HdbSql,
            $"-U \"{key}\" \"SELECT 'ok' FROM DUMMY\"");
        return result.Success && result.Output.Contains("ok");
    }

    /// <summary>
    /// Lists eligible schemas for the given userstore key.
    /// </summary>
    public static (bool Success, List<string> Schemas, string Error) ListSchemas(string key)
    {
        const string query =
            "SELECT SCHEMA_NAME FROM SCHEMAS " +
            "WHERE SCHEMA_OWNER = 'SYSTEM' " +
            "AND SCHEMA_NAME NOT IN ('_SYS_SECURITY', 'IFSERV', 'B1if', 'SYSTEM', 'RSP');";

        var result = RunAndCapture(HdbClientLocator.HdbSql, $"-U \"{key}\" \"{query}\"");
        if (!result.Success)
            return (false, [], result.Output);

        var schemas = new List<string>();
        foreach (var line in result.Output.Split('\n'))
        {
            var clean = line.Trim().Trim('"');
            if (string.IsNullOrWhiteSpace(clean)) continue;
            if (clean.StartsWith("SCHEMA_NAME", StringComparison.OrdinalIgnoreCase)) continue;
            if (clean.Contains("rows selected", StringComparison.OrdinalIgnoreCase)) continue;
            if (clean.Contains("row selected", StringComparison.OrdinalIgnoreCase)) continue;
            if (clean.Contains("overall time", StringComparison.OrdinalIgnoreCase)) continue;
            if (clean.StartsWith('-')) continue;
            schemas.Add(clean);
        }

        return (true, schemas, "");
    }

    /// <summary>
    /// Runs hdbsql with the given key and SQL query, streaming output lines
    /// to the provided callback. Returns the process exit code.
    /// The CancellationToken is checked — if cancelled, the process is terminated.
    /// </summary>
    public static async Task<int> RunSqlStreamingAsync(
        string key,
        string sql,
        Action<string> onOutputLine,
        CancellationToken ct)
    {
        return await RunStreamingAsync(
            HdbClientLocator.HdbSql,
            $"-U \"{key}\" \"{sql}\"",
            onOutputLine,
            ct);
    }

    /// <summary>
    /// Runs an arbitrary shell command, streaming output lines to the callback.
    /// Used for compression steps (tar/pigz).
    /// </summary>
    public static async Task<int> RunCommandStreamingAsync(
        string executable,
        string arguments,
        Action<string> onOutputLine,
        CancellationToken ct)
    {
        return await RunStreamingAsync(executable, arguments, onOutputLine, ct);
    }

    // -------------------------------------------------------------------------

    private static (bool Success, string Output) RunAndCapture(string exe, string args)
    {
        try
        {
            var psi = new ProcessStartInfo(exe)
            {
                Arguments = args,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };

            using var proc = Process.Start(psi);
            if (proc is null)
                return (false, "Failed to start process");

            var stdout = proc.StandardOutput.ReadToEnd();
            var stderr = proc.StandardError.ReadToEnd();
            proc.WaitForExit();

            if (proc.ExitCode == 0)
                return (true, stdout);
            else
                return (false, string.IsNullOrWhiteSpace(stderr) ? stdout : stderr);
        }
        catch (Exception ex)
        {
            return (false, ex.Message);
        }
    }

    private static async Task<int> RunStreamingAsync(
        string exe,
        string args,
        Action<string> onOutputLine,
        CancellationToken ct)
    {
        var psi = new ProcessStartInfo(exe)
        {
            Arguments = args,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };

        Process? proc;
        try
        {
            proc = Process.Start(psi);
        }
        catch (Exception ex)
        {
            onOutputLine($"[ERROR] Failed to start process: {ex.Message}");
            return -1;
        }

        if (proc is null)
        {
            onOutputLine("[ERROR] Process could not be started.");
            return -1;
        }

        // Register cancellation: SIGTERM first, then SIGKILL
        using var ctr = ct.Register(() =>
        {
            _ = Task.Run(async () =>
            {
                try
                {
                    onOutputLine("[WARN] Sending SIGTERM to process...");
                    proc.Kill(entireProcessTree: false); // sends SIGTERM on Linux
                    await Task.Delay(5000, CancellationToken.None);
                    if (!proc.HasExited)
                    {
                        onOutputLine("[WARN] Process did not exit, sending SIGKILL...");
                        proc.Kill(entireProcessTree: true);
                    }
                }
                catch { /* process may already be gone */ }
            }, CancellationToken.None);
        });

        // Stream stdout
        var stdoutTask = Task.Run(async () =>
        {
            string? line;
            while ((line = await proc.StandardOutput.ReadLineAsync(CancellationToken.None)) is not null)
                onOutputLine(line);
        }, CancellationToken.None);

        // Stream stderr as well (SAP HANA outputs progress to stderr)
        var stderrTask = Task.Run(async () =>
        {
            string? line;
            while ((line = await proc.StandardError.ReadLineAsync(CancellationToken.None)) is not null)
                onOutputLine(line);
        }, CancellationToken.None);

        await Task.WhenAll(stdoutTask, stderrTask);
        await proc.WaitForExitAsync(CancellationToken.None);

        return proc.ExitCode;
    }
}
