using ClaudeDo.Worker.Runner;

namespace ClaudeDo.Worker.Tests.Runner;

public sealed class ClaudeArgsBuilderTests
{
    private readonly ClaudeArgsBuilder _builder = new();

    [Fact]
    public void Default_Config_Produces_Base_Args()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null));
        Assert.Contains("-p", args);
        Assert.Contains("--output-format", args);
        Assert.Contains("stream-json", args);
        Assert.Contains("--verbose", args);
        Assert.Contains("--permission-mode", args);
        Assert.Contains("auto", args);
        Assert.DoesNotContain("--dangerously-skip-permissions", args);
        Assert.Contains("--json-schema", args);
        Assert.DoesNotContain("--model", args);
        Assert.DoesNotContain("--append-system-prompt", args);
        Assert.DoesNotContain("--agents", args);
        Assert.DoesNotContain("--resume", args);
        Assert.DoesNotContain("--effort", args);
    }

    [Fact]
    public void Effort_Adds_Effort_Flag()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null, Effort: "xhigh"));
        Assert.Contains("--effort", args);
        Assert.Equal("xhigh", args[args.ToList().IndexOf("--effort") + 1]);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Blank_Effort_Omits_The_Flag(string? effort)
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null, Effort: effort));
        Assert.DoesNotContain("--effort", args);
    }

    [Fact]
    public void Model_Adds_Model_Flag()
    {
        var args = _builder.Build(new ClaudeRunConfig("sonnet-4-6", null, null, null));
        Assert.Contains("--model", args);
        Assert.Contains("sonnet-4-6", args);
    }

    [Fact]
    public void SystemPrompt_Adds_Append_System_Prompt_Flag()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, "Be concise.", null, null));
        Assert.Contains("--append-system-prompt", args);
        Assert.Contains("Be concise.", args);
    }

    [Fact]
    public void AgentPath_Adds_Agents_Flag_As_Json()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, "/path/to/agent.md", null));
        Assert.Contains("--agents", args);
        Assert.Contains(args, a => a.Contains("/path/to/agent.md"));
    }

    [Fact]
    public void ResumeSessionId_Adds_Resume_Flag()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, "sess-abc-123"));
        Assert.Contains("--resume", args);
        Assert.Contains("sess-abc-123", args);
    }

    [Fact]
    public void All_Options_Set_Includes_All_Flags()
    {
        var args = _builder.Build(new ClaudeRunConfig("opus-4-6", "Be thorough.", "/agents/dev.md", "sess-xyz"));
        Assert.Contains("--model", args);
        Assert.Contains("opus-4-6", args);
        Assert.Contains("--append-system-prompt", args);
        Assert.Contains("--agents", args);
        Assert.Contains("--resume", args);
        Assert.Contains("sess-xyz", args);
        Assert.Contains("--json-schema", args);
    }

    [Fact]
    public void SystemPrompt_With_Quotes_Is_Passed_Verbatim()
    {
        var prompt = """Don't say "hello".""";
        var args = _builder.Build(new ClaudeRunConfig(null, prompt, null, null));
        Assert.Contains("--append-system-prompt", args);
        Assert.Contains(prompt, args);
    }

    [Fact]
    public void SystemPrompt_With_Newline_Is_Passed_As_Single_Element()
    {
        var prompt = "line1\nline2";
        var args = _builder.Build(new ClaudeRunConfig(
            Model: null,
            SystemPrompt: prompt,
            AgentPath: null,
            ResumeSessionId: null));

        var list = args.ToList();
        var idx = list.IndexOf("--append-system-prompt");
        Assert.True(idx >= 0);
        Assert.Equal(prompt, list[idx + 1]);
    }

    [Fact]
    public void SystemPrompt_With_Tab_Is_Passed_As_Single_Element()
    {
        var prompt = "col1\tcol2";
        var args = _builder.Build(new ClaudeRunConfig(
            Model: null,
            SystemPrompt: prompt,
            AgentPath: null,
            ResumeSessionId: null));

        Assert.Contains(prompt, args);
    }

    [Fact]
    public void MaxTurns_Adds_Flag_When_Positive()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null, MaxTurns: 25));
        Assert.Contains("--max-turns", args);
        Assert.Contains("25", args);
    }

    [Fact]
    public void MaxTurns_Omitted_When_Null_Or_ZeroOrLess()
    {
        var a = _builder.Build(new ClaudeRunConfig(null, null, null, null, MaxTurns: null));
        var b = _builder.Build(new ClaudeRunConfig(null, null, null, null, MaxTurns: 0));
        Assert.DoesNotContain("--max-turns", a);
        Assert.DoesNotContain("--max-turns", b);
    }

    [Fact]
    public void PermissionMode_bypass_Maps_To_Auto()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null, PermissionMode: "bypassPermissions"));
        Assert.Contains("--permission-mode", args);
        Assert.Contains("auto", args);
        Assert.DoesNotContain("--dangerously-skip-permissions", args);
    }

    [Fact]
    public void PermissionMode_acceptEdits_Emits_PermissionMode_Flag()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null, PermissionMode: "acceptEdits"));
        Assert.Contains("--permission-mode", args);
        Assert.Contains("acceptEdits", args);
        Assert.DoesNotContain("--dangerously-skip-permissions", args);
    }

    [Fact]
    public void PermissionMode_Null_Defaults_To_Auto()
    {
        var args = _builder.Build(new ClaudeRunConfig(null, null, null, null));
        Assert.Contains("--permission-mode", args);
        Assert.Contains("auto", args);
        Assert.DoesNotContain("--dangerously-skip-permissions", args);
    }

    [Theory]
    [InlineData("haiku", "auto", "acceptEdits")]
    [InlineData("haiku", null, "acceptEdits")]
    [InlineData("haiku", "bypassPermissions", "acceptEdits")]
    [InlineData("claude-haiku-4-5-20251001", "auto", "acceptEdits")]
    [InlineData("haiku", "acceptEdits", "acceptEdits")]
    [InlineData("haiku", "plan", "plan")]
    [InlineData("sonnet", "auto", "auto")]
    [InlineData("claude-sonnet-4-6", "auto", "auto")]
    [InlineData(null, "auto", "auto")]
    public void PermissionMode_ForHaikuModel_SubstitutesAutoWithAcceptEdits(
        string? model, string? requestedMode, string expectedMode)
    {
        // claude-cli 2.1.220 silently downgrades "--permission-mode auto" to the interactive
        // "default" mode when the resolved model is a haiku model (verified against the real
        // CLI's stream-json `init` event), which would block an unattended run forever.
        var args = _builder.Build(new ClaudeRunConfig(model, null, null, null, PermissionMode: requestedMode));
        var list = args.ToList();
        var idx = list.IndexOf("--permission-mode");
        Assert.True(idx >= 0);
        Assert.Equal(expectedMode, list[idx + 1]);
    }

    [Fact]
    public void Build_emits_mcpConfig_and_allowedTools_when_set()
    {
        var args = new ClaudeArgsBuilder().Build(new ClaudeRunConfig(
            Model: null, SystemPrompt: null, AgentPath: null, ResumeSessionId: null,
            McpConfigPath: "C:\\tmp\\t_mcp.json",
            AllowedTools: "mcp__claudedo_run__SuggestImprovement"));
        Assert.Contains("--mcp-config", args);
        Assert.Contains(args, a => a.Contains("t_mcp.json"));
        Assert.Contains("--allowedTools", args);
        Assert.Contains("mcp__claudedo_run__SuggestImprovement", args);
    }

    [Fact]
    public void SystemPrompt_ContainingDangerousFlag_IsPassedAsLiteral_NotAsFlag()
    {
        // If the system prompt contains "--dangerously-skip-permissions", it must arrive
        // as the VALUE of --append-system-prompt, not as a standalone CLI flag.
        const string dangerousPrompt = "--dangerously-skip-permissions";
        var args = _builder.Build(new ClaudeRunConfig(null, dangerousPrompt, null, null));

        var list = args.ToList();
        var flagIdx = list.IndexOf("--append-system-prompt");
        Assert.True(flagIdx >= 0, "--append-system-prompt flag must be present");
        // The dangerous string sits immediately after the flag as its value.
        Assert.Equal(dangerousPrompt, list[flagIdx + 1]);
        // It does NOT appear as a separate element (i.e., not treated as its own flag).
        Assert.Equal(1, list.Count(a => a == dangerousPrompt));
    }
}

public sealed class MergeInstructionsTests
{
    [Fact]
    public void All_Empty_Returns_Empty()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions(null, null, null);
        Assert.Equal("", s);
    }

    [Fact]
    public void Only_Global_Returns_Global()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions("global rule", null, null);
        Assert.Equal("global rule", s);
    }

    [Fact]
    public void Only_Task_Returns_Task()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions(null, null, "task rule");
        Assert.Equal("task rule", s);
    }

    [Fact]
    public void Global_And_Task_Are_Prepended_With_Separator()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions("global rule", null, "task rule");
        Assert.Equal("global rule\n\ntask rule", s);
    }

    [Fact]
    public void All_Three_Are_Joined_In_Order()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions("G", "L", "T");
        Assert.Equal("G\n\nL\n\nT", s);
    }

    [Fact]
    public void Whitespace_Only_Parts_Are_Skipped()
    {
        var s = ClaudeDo.Worker.Runner.TaskRunner.MergeInstructions("   ", "L", "\t\n");
        Assert.Equal("L", s);
    }

    [Fact]
    public void BuildFindingsPointer_ReturnsNullWhenNoIndexExists()
    {
        var dir = Path.Combine(Path.GetTempPath(), "cdo-ptr-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(dir);
        try
        {
            Assert.Null(ClaudeDo.Worker.Runner.TaskRunner.BuildFindingsPointer(dir));
            Assert.Null(ClaudeDo.Worker.Runner.TaskRunner.BuildFindingsPointer(null));
            Assert.Null(ClaudeDo.Worker.Runner.TaskRunner.BuildFindingsPointer("  "));
        }
        finally { Directory.Delete(dir, recursive: true); }
    }

    [Fact]
    public void BuildFindingsPointer_MentionsTheIndexPathWhenItExists()
    {
        var dir = Path.Combine(Path.GetTempPath(), "cdo-ptr-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(Path.Combine(dir, ".claudedo"));
        File.WriteAllText(Path.Combine(dir, ".claudedo", "INDEX.md"), "# Findings\n");
        try
        {
            var pointer = ClaudeDo.Worker.Runner.TaskRunner.BuildFindingsPointer(dir);
            Assert.NotNull(pointer);
            Assert.Contains("INDEX.md", pointer);
            Assert.Contains("save_finding", pointer);
        }
        finally { Directory.Delete(dir, recursive: true); }
    }
}
