using Sdt.Config;
using Sdt.Core;
using Sdt.Core.Debug;
using Sdt.Runner;
using System.Text.Json;
using Xunit;

namespace DevTool.Tests;

public sealed class DebugServicesTests
{
    [Fact]
    public async Task DebugRunner_MissingPrereqDeclined_ReturnsUserDeclined()
    {
        var runner = new DebugProfileRunner(
            new FakeProbeService(false),
            new FakeInstallerService(true));

        var profile = new DebugProfileDefinition
        {
            Id = "p1",
            Label = "Profile",
            Type = "python",
            Command = "python",
            Args = ["--version"],
            Requires = [new ToolRequirement { Tool = "python", InstallPolicy = InstallPolicy.Prompt }]
        };

        var result = await runner.RunAsync(
            profile,
            new DevToolConfig(),
            Directory.GetCurrentDirectory(),
            verbose: false,
            confirmInstallAsync: (_, _) => Task.FromResult(false),
            onOutput: (_, _) => { });

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

    [Fact]
    public async Task DiagnosticsBundle_WritesFiles()
    {
        var service = new DiagnosticsBundleService();
        var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(root);

        var request = new DiagnosticsBundleRequest(
            Category: "workflow",
            ProjectRoot: root,
            SummaryMessage: "failed",
            OutputLines: ["hello"],
            WorkflowSteps: [],
            Probes: [],
            DiagnosticsOptions: new DebugDiagnosticsOptions { OutputDir = ".sdt/debug" },
            Config: new DevToolConfig(),
            StopReason: ExecutionStopReason.CommandFailed);

        var result = await service.WriteBundleAsync(request);
        Assert.True(result.Success);
        Assert.True(File.Exists(Path.Combine(result.BundleDirectory, "summary.json")));
        Assert.True(File.Exists(Path.Combine(result.BundleDirectory, "output.log")));
    }

    [Fact]
    public async Task DiagnosticsBundle_EmptyAllowlist_CapturesNoEnvByDefault()
    {
        var service = new DiagnosticsBundleService();
        var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(root);

        var request = new DiagnosticsBundleRequest(
            Category: "workflow",
            ProjectRoot: root,
            SummaryMessage: "failed",
            OutputLines: [],
            WorkflowSteps: [],
            Probes: [],
            DiagnosticsOptions: new DebugDiagnosticsOptions
            {
                OutputDir = ".sdt/debug",
                IncludeAllEnv = false,
                CaptureEnvKeys = []
            },
            Config: new DevToolConfig(),
            StopReason: ExecutionStopReason.CommandFailed);

        var result = await service.WriteBundleAsync(request);
        Assert.True(result.Success);

        var envJson = await File.ReadAllTextAsync(Path.Combine(result.BundleDirectory, "env.json"));
        using var doc = JsonDocument.Parse(envJson);
        Assert.Empty(doc.RootElement.EnumerateObject());
    }

    [Fact]
    public async Task DiagnosticsBundle_Allowlist_CapturesOnlyListedKeys()
    {
        var service = new DiagnosticsBundleService();
        var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(root);

        Environment.SetEnvironmentVariable("SDT_TEST_ENV_A", "A");
        Environment.SetEnvironmentVariable("SDT_TEST_ENV_B", "B");

        var request = new DiagnosticsBundleRequest(
            Category: "workflow",
            ProjectRoot: root,
            SummaryMessage: "failed",
            OutputLines: [],
            WorkflowSteps: [],
            Probes: [],
            DiagnosticsOptions: new DebugDiagnosticsOptions
            {
                OutputDir = ".sdt/debug",
                IncludeAllEnv = false,
                CaptureEnvKeys = ["SDT_TEST_ENV_A"]
            },
            Config: new DevToolConfig(),
            StopReason: ExecutionStopReason.CommandFailed);

        var result = await service.WriteBundleAsync(request);
        Assert.True(result.Success);

        var envJson = await File.ReadAllTextAsync(Path.Combine(result.BundleDirectory, "env.json"));
        using var doc = JsonDocument.Parse(envJson);
        Assert.True(doc.RootElement.TryGetProperty("SDT_TEST_ENV_A", out _));
        Assert.False(doc.RootElement.TryGetProperty("SDT_TEST_ENV_B", out _));
    }

    [Fact]
    public async Task DebugRunner_EmitsRunEvents()
    {
        var runner = new DebugProfileRunner(
            new FakeProbeService(true),
            new FakeInstallerService(true));

        var profile = new DebugProfileDefinition
        {
            Id = "p1",
            Label = "Profile",
            Type = "python",
            Command = "python",
            Args = ["--version"],
            Requires = [new ToolRequirement { Tool = "python", InstallPolicy = InstallPolicy.Prompt }]
        };

        var events = new List<RunEvent>();
        var result = await runner.RunAsync(
            profile,
            new DevToolConfig(),
            Directory.GetCurrentDirectory(),
            verbose: false,
            confirmInstallAsync: (_, _) => Task.FromResult(false),
            onOutput: (_, _) => { },
            onEvent: events.Add);

        Assert.True(result.Success);
        Assert.Contains(events, e => e.Type == RunEventType.DebugStarted);
        Assert.Contains(events, e => e.Type == RunEventType.DebugCommandStarted);
        Assert.Contains(events, e => e.Type == RunEventType.DebugCommandCompleted);
        Assert.Contains(events, e => e.Type == RunEventType.DebugCompleted && e.Success == true);
    }

    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)));
    }
}
