using AgentSmith.Contracts.Commands;
using AgentSmith.Contracts.Models.Configuration;
using AgentSmith.Contracts.Services;
using AgentSmith.Domain.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace AgentSmith.Application.Services;

/// <summary>
/// Thin orchestrator: iterates commands, delegates each step to
/// <see cref="IPipelineStepRunner"/>, lazy-boots the sandbox via
/// <see cref="IPipelineSandboxCoordinator"/>, and routes failures through
/// <see cref="IPipelineErrorHandler"/>. Dynamic-command insertion lives here
/// because mutating the LinkedList is part of the iteration logic itself.
/// </summary>
public sealed class PipelineExecutor(
    IServiceProvider serviceProvider,
    IPipelineStepRunner stepRunner,
    IPipelineErrorHandler errorHandler,
    IPipelineLifecycleCoordinator lifecycleCoordinator,
    ILogger<PipelineExecutor> logger) : IPipelineExecutor
{
    private const int MaxCommandExecutions = 100;

    public async Task<CommandResult> ExecuteAsync(
        IReadOnlyList<string> commandNames, ResolvedProject projectConfig,
        PipelineContext context, CancellationToken cancellationToken)
    {
        logger.LogInformation("Starting pipeline with {Count} commands", commandNames.Count);
        for (var i = 0; i < commandNames.Count; i++)
            logger.LogInformation("  [{Index}/{Total}] {Command}", i + 1, commandNames.Count, commandNames[i]);

        await errorHandler.PostWorkingStatusAsync(projectConfig, context, cancellationToken);
        await using var lifecycle = await lifecycleCoordinator.BeginAsync(projectConfig, context, cancellationToken);
        await using var sandbox = serviceProvider.GetRequiredService<IPipelineSandboxCoordinator>();
        try { return await RunLoopAsync(commandNames, projectConfig, context, lifecycle, sandbox, cancellationToken); }
        catch { lifecycle.MarkFailed(); throw; }
    }

    private async Task<CommandResult> RunLoopAsync(
        IReadOnlyList<string> commandNames, ResolvedProject projectConfig, PipelineContext context,
        IAsyncPipelineLifecycle lifecycle, IPipelineSandboxCoordinator sandbox, CancellationToken ct)
    {
        var commands = new LinkedList<PipelineCommand>(commandNames.Select(PipelineCommand.Simple));
        var maxConcurrent = PipelineExecutorPolicy.ResolveMaxConcurrent(projectConfig, context);
        var current = commands.First;
        var executionCount = 0;

        while (current is not null)
        {
            var batch = stepRunner.PeelBatch(current, maxConcurrent);
            if (batch.Any(n => sandbox.IsSandboxRequiring(n.Value.Name)))
                await sandbox.EnsureSandboxAsync(projectConfig, context, ct);

            if (executionCount + batch.Count > MaxCommandExecutions)
            {
                lifecycle.MarkFailed();
                return CommandResult.Fail($"Pipeline exceeded maximum of {MaxCommandExecutions} command executions. " +
                                          "Possible infinite loop in command insertion.");
            }

            var stepResult = batch.Count == 1
                ? await stepRunner.RunSingleAsync(current, commands, projectConfig, context, ++executionCount, ct)
                : await stepRunner.RunBatchAsync(batch, commands, projectConfig, context, executionCount + 1, ct);
            if (batch.Count > 1) executionCount += batch.Count;

            if (!stepResult.Result.IsSuccess)
            {
                await errorHandler.HandleStepFailureAsync(commandNames, projectConfig, context, lifecycle, stepResult.Result, ct);
                return stepResult.Result;
            }
            if (PipelineExecutorPolicy.TryGetParkedReason(context, logger, out var parked)) return CommandResult.Ok(parked);
            current = stepResult.AdvanceTo ?? batch[^1].Next;
        }

        logger.LogInformation("Pipeline completed successfully");
        return CommandResult.Ok("Pipeline completed successfully");
    }
}
