using Sdt.Config;

namespace Sdt.Core;

public sealed class WorkflowExecutor(
    IWorkflowPlanner planner,
    IToolProbe toolProbe,
    IPrereqInstaller installer,
    IActionRunner actionRunner,
    IRequirementResolver requirementResolver)
{
    private readonly IWorkflowPlanner _planner = planner;
    private readonly IToolProbe _toolProbe = toolProbe;
    private readonly IPrereqInstaller _installer = installer;
    private readonly IActionRunner _actionRunner = actionRunner;
    private readonly IRequirementResolver _requirementResolver = requirementResolver;

    public async Task<WorkflowExecutionResult> ExecuteAsync(
        WorkflowDefinition rootWorkflow,
        IReadOnlyDictionary<string, WorkflowDefinition> allWorkflows,
        DevToolConfig config,
        string projectRoot,
        Func<string, InstallPlan, Task<bool>> confirmInstallAsync,
        Action<string, bool> onOutput,
        Action<RunEvent>? onEvent = null,
        CancellationToken cancellationToken = default)
    {
        var results = new List<WorkflowStepResult>();
        var plan = _planner.ResolvePlan(rootWorkflow, allWorkflows);
        onEvent?.Invoke(new RunEvent(
            Category: "workflow",
            Type: RunEventType.WorkflowStarted,
            Message: $"Workflow '{rootWorkflow.Id}' started.",
            WorkflowId: rootWorkflow.Id));
        onEvent?.Invoke(new RunEvent(
            Category: "workflow",
            Type: RunEventType.WorkflowPlanned,
            Message: $"Execution plan contains {plan.Count} workflow(s).",
            WorkflowId: rootWorkflow.Id));

        if (plan.Count == 0)
        {
            onEvent?.Invoke(new RunEvent(
                Category: "workflow",
                Type: RunEventType.WorkflowCompleted,
                Message: "No executable workflow steps were found.",
                WorkflowId: rootWorkflow.Id,
                Success: false));
            return new WorkflowExecutionResult(
                Success: false,
                StopReason: ExecutionStopReason.ValidationFailed,
                Message: "This workflow has no executable steps.",
                Steps: results);
        }

        foreach (var workflow in plan)
        {
            foreach (var step in workflow.Steps)
            {
                onEvent?.Invoke(new RunEvent(
                    Category: "workflow",
                    Type: RunEventType.WorkflowStepStarted,
                    Message: $"Step '{step.Label}' started.",
                    WorkflowId: workflow.Id,
                    StepId: step.Id));

                var requires = _requirementResolver.Resolve(step);
                foreach (var req in requires)
                {
                    var probe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false);
                    onEvent?.Invoke(new RunEvent(
                        Category: "workflow",
                        Type: RunEventType.ProbeChecked,
                        Message: probe.IsAvailable
                            ? $"Tool '{req.Tool}' is available."
                            : $"Tool '{req.Tool}' is missing.",
                        WorkflowId: workflow.Id,
                        StepId: step.Id,
                        Tool: req.Tool,
                        Success: probe.IsAvailable));
                    if (probe.IsAvailable)
                        continue;

                    if (!string.IsNullOrWhiteSpace(probe.Details))
                    {
                        onOutput($"Probe detail [{req.Tool}]: {probe.Details}", false);
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.ProbeFailed,
                            Message: probe.Details,
                            WorkflowId: workflow.Id,
                            StepId: step.Id,
                            Tool: req.Tool,
                            Success: false));
                    }

                    if (req.InstallPolicy == InstallPolicy.Never)
                    {
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.WorkflowCompleted,
                            Message: $"Missing prerequisite '{req.Tool}'.",
                            WorkflowId: rootWorkflow.Id,
                            Tool: req.Tool,
                            Success: false));
                        return new WorkflowExecutionResult(
                            Success: false,
                            StopReason: ExecutionStopReason.MissingPrereq,
                            Message: $"Missing prerequisite '{req.Tool}' for step '{step.Label}'.",
                            Steps: results);
                    }

                    var installPlan = await _installer.GetInstallPlanAsync(
                        req.Tool,
                        projectRoot,
                        config,
                        cancellationToken).ConfigureAwait(false);
                    onEvent?.Invoke(new RunEvent(
                        Category: "workflow",
                        Type: RunEventType.InstallPlanPrepared,
                        Message: installPlan.Summary,
                        WorkflowId: workflow.Id,
                        StepId: step.Id,
                        Tool: req.Tool,
                        Success: installPlan.Supported));
                    if (!installPlan.Supported || installPlan.Commands.Count == 0)
                    {
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.WorkflowCompleted,
                            Message: $"No installer plan available for '{req.Tool}'.",
                            WorkflowId: rootWorkflow.Id,
                            Tool: req.Tool,
                            Success: false));
                        return new WorkflowExecutionResult(
                            Success: false,
                            StopReason: ExecutionStopReason.MissingPrereq,
                            Message: $"Missing prerequisite '{req.Tool}' and no installer plan is available.",
                            Steps: results);
                    }

                    var approved = req.InstallPolicy == InstallPolicy.Auto
                        ? true
                        : await confirmInstallAsync(req.Tool, installPlan).ConfigureAwait(false);

                    if (!approved)
                    {
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.InstallDeclined,
                            Message: $"Install declined for '{req.Tool}'.",
                            WorkflowId: workflow.Id,
                            StepId: step.Id,
                            Tool: req.Tool,
                            Success: false));
                        return new WorkflowExecutionResult(
                            Success: false,
                            StopReason: ExecutionStopReason.UserDeclined,
                            Message: $"Install declined for missing prerequisite '{req.Tool}'.",
                            Steps: results);
                    }

                    foreach (var installCommand in installPlan.Commands)
                    {
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.InstallCommandStarted,
                            Message: $"{installCommand.Command} {string.Join(" ", installCommand.Args)}",
                            WorkflowId: workflow.Id,
                            StepId: step.Id,
                            Tool: req.Tool));

                        var installResult = await _installer.RunInstallAsync(
                            installCommand,
                            projectRoot,
                            onOutput,
                            cancellationToken).ConfigureAwait(false);
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.InstallCommandCompleted,
                            Message: $"Install command exited {installResult.ExitCode}.",
                            WorkflowId: workflow.Id,
                            StepId: step.Id,
                            Tool: req.Tool,
                            Success: installResult.Success,
                            ExitCode: installResult.ExitCode));

                        if (!installResult.Success)
                        {
                            onEvent?.Invoke(new RunEvent(
                                Category: "workflow",
                                Type: RunEventType.WorkflowCompleted,
                                Message: $"Install failed for '{req.Tool}'.",
                                WorkflowId: rootWorkflow.Id,
                                Tool: req.Tool,
                                Success: false));
                            return new WorkflowExecutionResult(
                                Success: false,
                                StopReason: ExecutionStopReason.InstallFailed,
                                Message: $"Failed to install prerequisite '{req.Tool}'.",
                                Steps: results);
                        }
                    }

                    var verifyProbe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false);
                    if (!verifyProbe.IsAvailable)
                    {
                        onEvent?.Invoke(new RunEvent(
                            Category: "workflow",
                            Type: RunEventType.WorkflowCompleted,
                            Message: $"Tool '{req.Tool}' still missing after install.",
                            WorkflowId: rootWorkflow.Id,
                            Tool: req.Tool,
                            Success: false));
                        return new WorkflowExecutionResult(
                            Success: false,
                            StopReason: ExecutionStopReason.InstallFailed,
                            Message: $"Prerequisite '{req.Tool}' is still missing after install attempt.",
                            Steps: results);
                    }
                }

                var runResult = await _actionRunner.RunStepAsync(
                    step,
                    projectRoot,
                    onOutput,
                    cancellationToken).ConfigureAwait(false);

                results.Add(new WorkflowStepResult(
                    workflow.Id,
                    step.Id,
                    string.IsNullOrWhiteSpace(step.Label) ? step.Id : step.Label,
                    runResult));
                onEvent?.Invoke(new RunEvent(
                    Category: "workflow",
                    Type: RunEventType.WorkflowStepCompleted,
                    Message: $"Step '{step.Label}' exited {runResult.ExitCode}.",
                    WorkflowId: workflow.Id,
                    StepId: step.Id,
                    Success: runResult.Success,
                    ExitCode: runResult.ExitCode));

                if (!runResult.Success)
                {
                    onEvent?.Invoke(new RunEvent(
                        Category: "workflow",
                        Type: RunEventType.WorkflowCompleted,
                        Message: $"Step '{step.Label}' failed.",
                        WorkflowId: rootWorkflow.Id,
                        Success: false,
                        ExitCode: runResult.ExitCode));
                    return new WorkflowExecutionResult(
                        Success: false,
                        StopReason: ExecutionStopReason.CommandFailed,
                        Message: $"Step '{step.Label}' failed with exit code {runResult.ExitCode}.",
                        Steps: results);
                }
            }
        }

        onEvent?.Invoke(new RunEvent(
            Category: "workflow",
            Type: RunEventType.WorkflowCompleted,
            Message: "Workflow completed successfully.",
            WorkflowId: rootWorkflow.Id,
            Success: true));
        return new WorkflowExecutionResult(
            Success: true,
            StopReason: null,
            Message: "Workflow completed successfully.",
            Steps: results);
    }
}
