using System.Diagnostics;
using System.Text.Json;
using Sdt.Config;

namespace Sdt.Core;

public sealed class ToolProbeService : IToolProbe
{
    public async Task<ProbeResult> ProbeAsync(
        string tool,
        string projectRoot,
        DevToolConfig? config = null,
        CancellationToken cancellationToken = default)
    {
        var direct = await ProbeDirectAsync(tool, config, cancellationToken).ConfigureAwait(false);
        if (direct.IsAvailable)
            return direct;

        var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "diag.py");
        if (scriptPath is null)
            return direct;

        if (!(await ProbeDirectAsync("python", config, cancellationToken).ConfigureAwait(false)).IsAvailable)
            return direct;

        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = PythonResolver.ResolveExecutable(),
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                WorkingDirectory = projectRoot,
            };
            psi.ArgumentList.Add(scriptPath);
            psi.ArgumentList.Add("probe");
            psi.ArgumentList.Add("--tool");
            psi.ArgumentList.Add(tool);
            psi.ArgumentList.Add("--json");

            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
            if (process.ExitCode != 0)
                return new ProbeResult(tool, false, Details: stderr.Trim());

            var parsed = JsonSerializer.Deserialize<DiagProbeJson>(stdout);
            if (parsed is null)
                return new ProbeResult(tool, false, Details: "diag.py returned invalid JSON");

            return new ProbeResult(parsed.Tool ?? tool, parsed.Available, parsed.Version, parsed.Details);
        }
        catch (Exception ex)
        {
            return new ProbeResult(tool, false, Details: ex.Message);
        }
    }

    private static async Task<ProbeResult> ProbeDirectAsync(string tool, DevToolConfig? config, CancellationToken cancellationToken)
    {
        var command = tool.ToLowerInvariant() switch
        {
            "python" => PythonResolver.ResolveExecutable(),
            "dotnet" => "dotnet",
            "node" => "node",
            "npm" => "npm",
            "cargo" => "cargo",
            "tauri" => "tauri",
            "git" => "git",
            "docker" => "docker",
            _ => tool,
        };
        var resolution = CommandResolver.ResolveWithTrace(command, config, tool);
        command = resolution.Resolved;

        var versionArg = command is "python" ? "--version" : "--version";
        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = command,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            psi.ArgumentList.Add(versionArg);

            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

            if (process.ExitCode != 0)
            {
                var failDetails = string.IsNullOrWhiteSpace(stderr) ? stdout.Trim() : stderr.Trim();
                var trace = $"{resolution.Source}: {resolution.Resolved}";
                return new ProbeResult(tool, false, Details: string.IsNullOrWhiteSpace(failDetails) ? trace : $"{trace} | {failDetails}");
            }

            var version = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim();
            return new ProbeResult(tool, true, Version: version, Details: $"{resolution.Source}: {resolution.Resolved}");
        }
        catch (Exception ex)
        {
            return new ProbeResult(tool, false, Details: $"{resolution.Source}: {resolution.Resolved} | {ex.Message}");
        }
    }

    private sealed class DiagProbeJson
    {
        public string? Tool { get; init; }
        public bool Available { get; init; }
        public string? Version { get; init; }
        public string? Details { get; init; }
    }
}
