using Sdt.Config;
using Sdt.Core;
using Sdt.Runner;
using Xunit;

namespace DevTool.Tests;

public sealed class WorkflowExecutorTests
{
    [Fact]
    public async Task MissingPrereq_UserDeclines_ReturnsUserDeclined()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new FakeProbeService(isAvailable: false),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var cfg = new DevToolConfig();
        var wf = BuildSingleStepWorkflow("w", "dotnet");
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };

        var result = await executor.ExecuteAsync(
            wf, map, cfg, ".", (_, _) => Task.FromResult(false), (_, _) => { });

        Assert.False(result.Success);
        Assert.Equal(ExecutionStopReason.UserDeclined, result.StopReason);
    }

    [Fact]
    public async Task MissingPrereq_InstallFails_ReturnsInstallFailed()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new FakeProbeService(isAvailable: false),
            new FakeInstallerService(success: false),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var cfg = new DevToolConfig();
        var wf = BuildSingleStepWorkflow("w", "dotnet");
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };

        var result = await executor.ExecuteAsync(
            wf, map, cfg, ".", (_, _) => Task.FromResult(true), (_, _) => { });

        Assert.False(result.Success);
        Assert.Equal(ExecutionStopReason.InstallFailed, result.StopReason);
    }

    [Fact]
    public async Task StepFailure_StopsImmediately()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new FakeProbeService(isAvailable: true),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: false),
            new RequirementResolver());

        var wf = new WorkflowDefinition
        {
            Id = "w",
            Label = "W",
            Steps =
            [
                new WorkflowStep { Id = "s1", Label = "S1", Command = "dotnet", Args = ["build"] },
                new WorkflowStep { Id = "s2", Label = "S2", Command = "dotnet", Args = ["build"] },
            ]
        };
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };

        var result = await executor.ExecuteAsync(
            wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { });

        Assert.False(result.Success);
        Assert.Equal(ExecutionStopReason.CommandFailed, result.StopReason);
        Assert.Single(result.Steps);
    }

    [Fact]
    public async Task LegacyPwshScriptStep_MissingPrereq_PromptsBeforeRun()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new FakeProbeService(isAvailable: false),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var wf = new WorkflowDefinition
        {
            Id = "w",
            Label = "W",
            Steps =
            [
                new WorkflowStep
                {
                    Id = "ps1",
                    Label = "Legacy PS1",
                    Command = "pwsh",
                    Args = ["-NoProfile", "-File", "scripts/publish-app.ps1", "-Target", "web"],
                }
            ]
        };
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };

        var result = await executor.ExecuteAsync(
            wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(false), (_, _) => { });

        Assert.False(result.Success);
        Assert.Equal(ExecutionStopReason.UserDeclined, result.StopReason);
    }

    [Fact]
    public async Task TauriBuild_DoesNotRequireGlobalTauri_WhenNodeNpmCargoAvailable()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new ConditionalProbeService(),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var wf = new WorkflowDefinition
        {
            Id = "w",
            Label = "W",
            Steps =
            [
                new WorkflowStep
                {
                    Id = "tauri",
                    Label = "Tauri Build",
                    Action = "tauri-build",
                }
            ]
        };
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };

        var result = await executor.ExecuteAsync(
            wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { });

        Assert.True(result.Success);
        Assert.Null(result.StopReason);
        Assert.Single(result.Steps);
    }

    [Fact]
    public async Task MissingPrereq_EmitsProbeDiagnosticsToOutput()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new DetailedProbeService(),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var wf = BuildSingleStepWorkflow("w", "dotnet");
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };
        var lines = new List<string>();

        var result = await executor.ExecuteAsync(
            wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(false), (line, _) => lines.Add(line));

        Assert.False(result.Success);
        Assert.Contains(lines, l => l.Contains("Probe detail [dotnet]", StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public async Task ExecuteAsync_EmitsRunEvents_ForStepLifecycle()
    {
        var executor = new WorkflowExecutor(
            new WorkflowPlanner(),
            new FakeProbeService(isAvailable: true),
            new FakeInstallerService(success: true),
            new FakeActionRunner(success: true),
            new RequirementResolver());

        var wf = BuildSingleStepWorkflow("w", "dotnet");
        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf };
        var events = new List<RunEvent>();

        var result = await executor.ExecuteAsync(
            wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { }, events.Add);

        Assert.True(result.Success);
        Assert.Contains(events, e => e.Type == RunEventType.WorkflowStarted);
        Assert.Contains(events, e => e.Type == RunEventType.WorkflowStepStarted);
        Assert.Contains(events, e => e.Type == RunEventType.WorkflowStepCompleted);
        Assert.Contains(events, e => e.Type == RunEventType.WorkflowCompleted && e.Success == true);
    }

    [Fact]
    public void AggregatorWorkflow_ExecutesDependenciesOnly()
    {
        var planner = new WorkflowPlanner();
        var dep = new WorkflowDefinition
        {
            Id = "dep",
            Label = "Dependency",
            Steps = [new WorkflowStep { Id = "s", Label = "S", Command = "dotnet", Args = ["build"] }]
        };
        var agg = new WorkflowDefinition
        {
            Id = "agg",
            Label = "Aggregator",
            DependsOn = ["dep"],
            Steps = []
        };

        var map = new Dictionary<string, WorkflowDefinition>(StringComparer.OrdinalIgnoreCase)
        {
            [dep.Id] = dep,
            [agg.Id] = agg
        };

        var plan = planner.ResolvePlan(agg, map);

        Assert.Single(plan);
        Assert.Equal("dep", plan[0].Id);
    }

    private static WorkflowDefinition BuildSingleStepWorkflow(string id, string tool)
    {
        return new WorkflowDefinition
        {
            Id = id,
            Label = id,
            Steps =
            [
                new WorkflowStep
                {
                    Id = "step",
                    Label = "step",
                    Command = tool,
                    Args = ["--version"],
                    Requires = [new ToolRequirement { Tool = tool, InstallPolicy = InstallPolicy.Prompt }],
                }
            ]
        };
    }

    private sealed class FakeProbeService(bool isAvailable) : IToolProbe
    {
        public Task<ProbeResult> ProbeAsync(
            string tool,
            string projectRoot,
            DevToolConfig? config = null,
            CancellationToken cancellationToken = default)
            => Task.FromResult(new ProbeResult(tool, isAvailable, Version: isAvailable ? "1.0.0" : null));
    }

    private sealed class FakeInstallerService(bool success) : IPrereqInstaller
    {
        public Task<InstallPlan> GetInstallPlanAsync(
            string tool,
            string projectRoot,
            DevToolConfig? config = null,
            CancellationToken cancellationToken = default)
            => Task.FromResult(new InstallPlan(tool, Supported: true, "test", [new InstallCommand("echo", ["ok"])]));

        public Task<RunResult> RunInstallAsync(
            InstallCommand command,
            string projectRoot,
            Action<string, bool> onOutput,
            CancellationToken cancellationToken = default)
            => Task.FromResult(new RunResult(success ? 0 : 1, TimeSpan.FromMilliseconds(5)));
    }

    private sealed class FakeActionRunner(bool success) : IActionRunner
    {
        public Task<RunResult> RunStepAsync(
            WorkflowStep step,
            string projectRoot,
            Action<string, bool> onOutput,
            CancellationToken cancellationToken = default)
            => Task.FromResult(new RunResult(success ? 0 : 2, TimeSpan.FromMilliseconds(10)));
    }

    private sealed class ConditionalProbeService : IToolProbe
    {
        public Task<ProbeResult> ProbeAsync(
            string tool,
            string projectRoot,
            DevToolConfig? config = null,
            CancellationToken cancellationToken = default)
        {
            var available = tool.ToLowerInvariant() switch
            {
                "node" => true,
                "npm" => true,
                "cargo" => true,
                "tauri" => false,
                _ => true
            };
            return Task.FromResult(new ProbeResult(tool, available, Version: available ? "1.0.0" : null));
        }
    }

    private sealed class DetailedProbeService : IToolProbe
    {
        public Task<ProbeResult> ProbeAsync(
            string tool,
            string projectRoot,
            DevToolConfig? config = null,
            CancellationToken cancellationToken = default)
            => Task.FromResult(new ProbeResult(tool, false, Details: "Fallback: unresolved command"));
    }
}
