using ClaudeDo.Installer.Checks;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Tests.Checks;

namespace ClaudeDo.Installer.Tests.Core;

public sealed class ClaudeHelpLauncherTests : IDisposable
{
    private readonly string _dir;

    public ClaudeHelpLauncherTests()
    {
        _dir = Path.Combine(Path.GetTempPath(), $"cdclaudehelp_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_dir);
    }

    public void Dispose()
    {
        try { Directory.Delete(_dir, recursive: true); } catch { }
    }

    private ClaudeHelpLauncher MakeLauncher(FakeProcessRunner? runner = null, FakeProcessLauncher? launcher = null, string? pathExtOverride = ".exe") =>
        new(runner ?? new FakeProcessRunner(), launcher ?? new FakeProcessLauncher(), pathOverride: _dir, pathExtOverride: pathExtOverride);

    private static EnvironmentCheckReport MakeReport() => new(new[]
    {
        CheckResult.Ok(GitCheck.CheckId, CheckSeverity.Error, "checks.git.title", "C:\\Program Files\\Git\\git.exe — 2.43.0"),
        CheckResult.Ok(ClaudeCliCheck.CheckId, CheckSeverity.Error, "checks.claudeCli.title", "C:\\npm\\claude.cmd — 2.1.230"),
        CheckResult.Fail(ClaudeAuthCheck.CheckId, CheckSeverity.Error, "checks.claudeAuth.title", "Not logged in.",
            "checks.claudeAuth.hint", detail: "exit code 1: some raw CLI output"),
    });

    // ----- BuildReportAsync -----

    [Fact]
    public async Task Report_includes_all_check_ids_severities_and_failed_details()
    {
        var launcher = MakeLauncher();
        var report = MakeReport();

        var text = await launcher.BuildReportAsync(report, new InstallContext(), CancellationToken.None);

        Assert.Contains(GitCheck.CheckId, text);
        Assert.Contains(ClaudeCliCheck.CheckId, text);
        Assert.Contains(ClaudeAuthCheck.CheckId, text);
        Assert.Contains(CheckSeverity.Error.ToString(), text);
        Assert.Contains(CheckStatus.Failed.ToString(), text);
        Assert.Contains("exit code 1: some raw CLI output", text);
    }

    [Fact]
    public async Task Report_does_not_include_environment_variable_values()
    {
        const string marker = "sk-super-secret-token-12345";
        Environment.SetEnvironmentVariable("CLAUDEDO_TEST_SECRET", marker);
        try
        {
            var launcher = MakeLauncher();
            var text = await launcher.BuildReportAsync(MakeReport(), new InstallContext(), CancellationToken.None);

            Assert.DoesNotContain(marker, text);
            Assert.DoesNotContain(".credentials.json", text);
        }
        finally
        {
            Environment.SetEnvironmentVariable("CLAUDEDO_TEST_SECRET", null);
        }
    }

    [Fact]
    public async Task Report_includes_planned_install_directory_and_ports()
    {
        var launcher = MakeLauncher();
        var ctx = new InstallContext { InstallDirectory = @"C:\Somewhere\ClaudeDo", SignalRPort = 11111, ExternalMcpPort = 22222 };

        var text = await launcher.BuildReportAsync(MakeReport(), ctx, CancellationToken.None);

        Assert.Contains(@"C:\Somewhere\ClaudeDo", text);
        Assert.Contains("11111", text);
        Assert.Contains("22222", text);
    }

    // ----- LaunchTerminal -----

    private string MakeClaudeExe()
    {
        var path = Path.Combine(_dir, "claude.exe");
        File.WriteAllText(path, "");
        return path;
    }

    [Fact]
    public void Uses_wt_when_available()
    {
        MakeClaudeExe();
        File.WriteAllText(Path.Combine(_dir, "wt.exe"), "");
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher);

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), new InstallContext());

        Assert.True(result.Success);
        Assert.Contains("wt.exe", fakeLauncher.LastStartInfo!.FileName);
    }

    [Fact]
    public void Falls_back_to_cmd_when_wt_is_not_available()
    {
        MakeClaudeExe();
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher);

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), new InstallContext());

        Assert.True(result.Success);
        Assert.Equal("cmd.exe", fakeLauncher.LastStartInfo!.FileName);
    }

    [Fact]
    public void Working_directory_is_temp_not_install_directory()
    {
        MakeClaudeExe();
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher);
        var ctx = new InstallContext { InstallDirectory = @"C:\Program Files\ClaudeDo" };

        launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), ctx);

        Assert.Equal(Path.GetTempPath(), fakeLauncher.LastStartInfo!.WorkingDirectory);
        Assert.DoesNotContain(ctx.InstallDirectory, fakeLauncher.LastStartInfo!.WorkingDirectory);
    }

    [Fact]
    public void Quotes_the_claude_path_when_it_contains_spaces()
    {
        var spacedDir = Path.Combine(_dir, "path with spaces");
        Directory.CreateDirectory(spacedDir);
        var claudePath = Path.Combine(spacedDir, "claude.exe");
        File.WriteAllText(claudePath, "");
        var fakeLauncher = new FakeProcessLauncher();
        var runner = new FakeProcessRunner();
        var launcher = new ClaudeHelpLauncher(runner, fakeLauncher, pathOverride: spacedDir, pathExtOverride: ".exe");

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), new InstallContext());

        Assert.True(result.Success);
        Assert.Contains($"\"{claudePath}\"", fakeLauncher.LastStartInfo!.Arguments);
    }

    [Fact]
    public void Cmd_shim_as_claude_works()
    {
        var claudePath = Path.Combine(_dir, "claude.cmd");
        File.WriteAllText(claudePath, "");
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher, pathExtOverride: ".cmd");

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), new InstallContext());

        Assert.True(result.Success);
        Assert.Contains(claudePath, fakeLauncher.LastStartInfo!.Arguments);
    }

    [Fact]
    public void Claude_not_found_returns_error_result_not_exception()
    {
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher);
        var ctx = new InstallContext { ClaudeBin = "claudedo_totally_missing_cmd_9f3a1" };

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), ctx);

        Assert.False(result.Success);
        Assert.NotNull(result.ErrorMessage);
        Assert.Null(fakeLauncher.LastStartInfo);
    }

    [Fact]
    public void Failed_process_start_returns_error_result_not_exception()
    {
        MakeClaudeExe();
        var fakeLauncher = new FakeProcessLauncher(throwOnStart: new InvalidOperationException("boom"));
        var launcher = MakeLauncher(launcher: fakeLauncher);

        var result = launcher.LaunchTerminal(Path.Combine(_dir, "report.md"), new InstallContext());

        Assert.False(result.Success);
        Assert.Equal("boom", result.ErrorMessage);
    }

    // ----- LaunchAsync (report + terminal wiring) -----

    [Fact]
    public async Task LaunchAsync_writes_report_and_points_the_prompt_at_it()
    {
        MakeClaudeExe();
        var fakeLauncher = new FakeProcessLauncher();
        var launcher = MakeLauncher(launcher: fakeLauncher);

        var result = await launcher.LaunchAsync(MakeReport(), new InstallContext(), CancellationToken.None);

        var reportPath = Path.Combine(Path.GetTempPath(), ClaudeHelpLauncher.ReportFileName);
        try
        {
            Assert.True(result.Success);
            Assert.True(File.Exists(reportPath));
            Assert.Contains(GitCheck.CheckId, await File.ReadAllTextAsync(reportPath));
            Assert.Contains(reportPath, fakeLauncher.LastStartInfo!.Arguments);
        }
        finally
        {
            try { File.Delete(reportPath); } catch { }
        }
    }
}
