using System.Diagnostics;
using System.Text.Json;
using Journal.Core.Models;

namespace Journal.Core.Services.Sidecar;

public sealed class PythonSidecarClient(JournalConfig config)
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    private readonly JournalConfig _config = config;

    public async Task<JsonElement?> SendAsync(string action, object payload, CancellationToken cancellationToken)
    {
        var request = JsonSerializer.Serialize(new { action, payload }, JsonOptions);

        using var process = new Process();
        process.StartInfo = new ProcessStartInfo
        {
            FileName = _config.PythonExecutable,
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            WorkingDirectory = _config.ProjectRoot
        };
        process.StartInfo.ArgumentList.Add(_config.PythonAiSidecarPath);

        if (!process.Start())
            throw new InvalidOperationException("Failed to start Python sidecar process.");

        await process.StandardInput.WriteLineAsync(request);
        process.StandardInput.Close();

        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeoutCts.CancelAfter(_config.AiSidecarTimeoutMs);

        try
        {
            await process.WaitForExitAsync(timeoutCts.Token);
        }
        catch (OperationCanceledException)
        {
            TryKill(process);
            throw new TimeoutException($"Python sidecar timed out after {_config.AiSidecarTimeoutMs} ms.");
        }

        var stdout = await process.StandardOutput.ReadToEndAsync();
        var stderr = await process.StandardError.ReadToEndAsync();
        var line = LastJsonLine(stdout);
        if (string.IsNullOrWhiteSpace(line))
            throw new InvalidOperationException($"Python sidecar returned no JSON response. stderr: {stderr}".Trim());

        JsonDocument doc;
        try
        {
            doc = JsonDocument.Parse(line);
        }
        catch (JsonException ex)
        {
            throw new InvalidOperationException($"Invalid JSON from Python sidecar: {line}", ex);
        }

        using (doc)
        {
            var root = doc.RootElement;
            if (!root.TryGetProperty("ok", out var okNode) || okNode.ValueKind != JsonValueKind.True && okNode.ValueKind != JsonValueKind.False)
                throw new InvalidOperationException("Python sidecar response missing boolean 'ok' field.");

            if (!okNode.GetBoolean())
            {
                var error = root.TryGetProperty("error", out var errorNode)
                    ? errorNode.GetString() ?? "Unknown sidecar error."
                    : "Unknown sidecar error.";
                throw new InvalidOperationException(error);
            }

            if (!root.TryGetProperty("data", out var dataNode))
                return null;

            return dataNode.Clone();
        }
    }

    private static string LastJsonLine(string text)
    {
        var lines = text.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
        for (var i = lines.Length - 1; i >= 0; i--)
        {
            var line = lines[i].Trim();
            if (line.StartsWith('{') && line.EndsWith('}'))
                return line;
        }

        return "";
    }

    private static void TryKill(Process process)
    {
        try
        {
            if (!process.HasExited)
                process.Kill(entireProcessTree: true);
        }
        catch
        {
            // Ignore cleanup errors while handling timeout/failure path.
        }
    }
}
