using Sdt.Config;
using Sdt.Runner;

namespace Sdt.Core.Debug;

public sealed class DebugProfileRunner(
    IToolProbe toolProbe,
    IPrereqInstaller installer) : IDebugProfileRunner
{
    private readonly IToolProbe _toolProbe = toolProbe;
    private readonly IPrereqInstaller _installer = installer;

    public async Task<DebugRunResult> RunAsync(
        DebugProfileDefinition profile,
        DevToolConfig config,
        string projectRoot,
        bool verbose,
        Func<string, InstallPlan, Task<bool>> confirmInstallAsync,
        Action<string, bool> onOutput,
        Action<RunEvent>? onEvent = null,
        CancellationToken cancellationToken = default)
    {
        var probes = new List<ProbeResult>();
        var output = new List<string>();
        onEvent?.Invoke(new RunEvent(
            Category: "debug",
            Type: RunEventType.DebugStarted,
            Message: $"Debug profile '{profile.Id}' started."));
        var requires = profile.Requires.Count > 0
            ? profile.Requires
            : InferRequirements(profile);

        foreach (var req in requires)
        {
            var probe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false);
            probes.Add(probe);
            if (probe.IsAvailable)
                continue;

            if (!string.IsNullOrWhiteSpace(probe.Details))
            {
                var line = $"Probe detail [{req.Tool}]: {probe.Details}";
                output.Add("OUT: " + line);
                if (verbose)
                    onOutput(line, false);
            }

            if (req.InstallPolicy == InstallPolicy.Never)
            {
                onEvent?.Invoke(new RunEvent(
                    Category: "debug",
                    Type: RunEventType.DebugCompleted,
                    Message: $"Missing prerequisite '{req.Tool}'.",
                    Tool: req.Tool,
                    Success: false));
                return new DebugRunResult(
                    Success: false,
                    StopReason: ExecutionStopReason.MissingPrereq,
                    Message: $"Missing prerequisite '{req.Tool}' for debug profile '{profile.Label}'.",
                    Profile: profile,
                    RunResult: null,
                    OutputLines: output,
                    Probes: probes);
            }

            var installPlan = await _installer.GetInstallPlanAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false);
            if (!installPlan.Supported || installPlan.Commands.Count == 0)
            {
                onEvent?.Invoke(new RunEvent(
                    Category: "debug",
                    Type: RunEventType.DebugCompleted,
                    Message: $"No installer plan available for '{req.Tool}'.",
                    Tool: req.Tool,
                    Success: false));
                return new DebugRunResult(
                    Success: false,
                    StopReason: ExecutionStopReason.MissingPrereq,
                    Message: $"Missing prerequisite '{req.Tool}' and no installer plan is available.",
                    Profile: profile,
                    RunResult: null,
                    OutputLines: output,
                    Probes: probes);
            }

            var approved = req.InstallPolicy == InstallPolicy.Auto
                ? true
                : await confirmInstallAsync(req.Tool, installPlan).ConfigureAwait(false);
            if (!approved)
            {
                onEvent?.Invoke(new RunEvent(
                    Category: "debug",
                    Type: RunEventType.InstallDeclined,
                    Message: $"Install declined for '{req.Tool}'.",
                    Tool: req.Tool,
                    Success: false));
                return new DebugRunResult(
                    Success: false,
                    StopReason: ExecutionStopReason.UserDeclined,
                    Message: $"Install declined for missing prerequisite '{req.Tool}'.",
                    Profile: profile,
                    RunResult: null,
                    OutputLines: output,
                    Probes: probes);
            }

            foreach (var cmd in installPlan.Commands)
            {
                var installResult = await _installer.RunInstallAsync(cmd, projectRoot, onOutput, cancellationToken).ConfigureAwait(false);
                if (!installResult.Success)
                {
                    onEvent?.Invoke(new RunEvent(
                        Category: "debug",
                        Type: RunEventType.DebugCompleted,
                        Message: $"Install failed for '{req.Tool}'.",
                        Tool: req.Tool,
                        Success: false,
                        ExitCode: installResult.ExitCode));
                    return new DebugRunResult(
                        Success: false,
                        StopReason: ExecutionStopReason.InstallFailed,
                        Message: $"Failed to install prerequisite '{req.Tool}'.",
                        Profile: profile,
                        RunResult: installResult,
                        OutputLines: output,
                        Probes: probes);
                }
            }
        }

        var cwd = Path.GetFullPath(Path.Combine(projectRoot, profile.WorkingDir));
        var mergedEnv = profile.Env.Count > 0 ? profile.Env : null;
        onEvent?.Invoke(new RunEvent(
            Category: "debug",
            Type: RunEventType.DebugCommandStarted,
            Message: $"{profile.Command} {string.Join(" ", profile.Args)}"));
        var run = await ProcessRunner.RunAsync(
            profile.Command,
            profile.Args,
            cwd,
            (line, isErr) =>
            {
                output.Add((isErr ? "ERR: " : "OUT: ") + line);
                if (verbose)
                    onOutput(line, isErr);
            },
            mergedEnv,
            cancellationToken).ConfigureAwait(false);
        onEvent?.Invoke(new RunEvent(
            Category: "debug",
            Type: RunEventType.DebugCommandCompleted,
            Message: $"Debug command exited {run.ExitCode}.",
            Success: run.Success,
            ExitCode: run.ExitCode));
        onEvent?.Invoke(new RunEvent(
            Category: "debug",
            Type: RunEventType.DebugCompleted,
            Message: run.Success ? "Debug run completed." : "Debug run failed.",
            Success: run.Success,
            ExitCode: run.ExitCode));

        return new DebugRunResult(
            Success: run.Success,
            StopReason: run.Success ? null : ExecutionStopReason.CommandFailed,
            Message: run.Success
                ? $"Debug profile '{profile.Label}' completed."
                : $"Debug profile '{profile.Label}' exited with code {run.ExitCode}.",
            Profile: profile,
            RunResult: run,
            OutputLines: output,
            Probes: probes);
    }

    private static List<ToolRequirement> InferRequirements(DebugProfileDefinition profile)
    {
        return profile.Type.ToLowerInvariant() switch
        {
            "dotnet" => [new ToolRequirement { Tool = "dotnet" }],
            "node" => [new ToolRequirement { Tool = "node" }, new ToolRequirement { Tool = "npm" }],
            "python" => [new ToolRequirement { Tool = "python" }],
            _ => string.IsNullOrWhiteSpace(profile.Command)
                ? []
                : [new ToolRequirement { Tool = profile.Command }],
        };
    }
}
