"""Tests for the shell_integration command."""

from pathlib import Path

from click.testing import CliRunner

from erk.cli.cli import cli


def test_shell_integration_with_switch() -> None:
    """Test shell integration with switch command."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "switch", "test"])
    # Should handle the command
    assert result.exit_code in (0, 1)  # May fail due to missing config, which is OK for this test


def test_shell_integration_with_passthrough() -> None:
    """Test shell integration passthrough for non-switch commands."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "list"])
    # Should either passthrough or handle
    assert result.exit_code in (0, 1)


def test_shell_integration_with_help() -> None:
    """Test shell integration with help command."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "--help"])
    # Should handle or passthrough
    assert result.exit_code in (0, 1)


def test_shell_integration_with_no_args() -> None:
    """Test shell integration with no arguments."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell"])
    # Should handle empty args gracefully
    assert result.exit_code in (0, 1)


def test_shell_integration_passthrough_marker() -> None:
    """Test that passthrough commands print the passthrough marker."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "list"])
    # If it's a passthrough, should contain the marker
    # Otherwise, it's being handled directly
    assert result.exit_code in (0, 1)


def test_shell_integration_sync_returns_script_by_default() -> None:
    """Sync passthrough should return a script path instead of executing inline."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "sync"])
    assert result.exit_code == 0
    script_output = result.output.strip()
    assert script_output
    script_path = Path(script_output)
    try:
        assert script_path.exists()
    finally:
        script_path.unlink(missing_ok=True)


def test_shell_integration_unknown_command() -> None:
    """Test shell integration with unknown command."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "unknown-command", "arg1"])
    # Should handle or passthrough unknown commands
    assert result.exit_code in (0, 1)


def test_shell_integration_sync_generates_posix_passthrough_script(tmp_path: Path) -> None:
    """When invoked from bash/zsh, __shell should return a passthrough script."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "sync"], env={"ERK_SHELL": "bash"})
    assert result.exit_code == 0
    script_output = result.output.strip()
    assert script_output
    script_path = Path(script_output)
    try:
        content = script_path.read_text(encoding="utf-8")
        assert "command erk sync" in content
        assert "__erk_exit=$?" in content
    finally:
        script_path.unlink(missing_ok=True)


def test_shell_integration_sync_generates_fish_passthrough_script(tmp_path: Path) -> None:
    """When invoked from fish, __shell should return a fish-compatible script."""
    runner = CliRunner()
    result = runner.invoke(cli, ["__shell", "sync"], env={"ERK_SHELL": "fish"})
    assert result.exit_code == 0
    script_output = result.output.strip()
    assert script_output
    script_path = Path(script_output)
    try:
        content = script_path.read_text(encoding="utf-8")
        assert 'command erk "sync"' in content
        assert "set __erk_exit $status" in content
    finally:
        script_path.unlink(missing_ok=True)


def test_shell_integration_fish_escapes_special_characters(tmp_path: Path) -> None:
    """Fish passthrough script should escape characters that trigger expansions."""
    runner = CliRunner()
    special_arg = "$branch;rm"
    second_arg = "(test)"
    result = runner.invoke(
        cli,
        ["__shell", "sync", special_arg, second_arg],
        env={"ERK_SHELL": "fish"},
    )
    assert result.exit_code == 0
    script_output = result.output.strip()
    assert script_output
    script_path = Path(script_output)
    try:
        content = script_path.read_text(encoding="utf-8")
        assert 'command erk "sync" "\\$branch\\;rm" "\\(test\\)"' in content
    finally:
        script_path.unlink(missing_ok=True)


def test_shell_integration_forwards_stderr_on_success() -> None:
    """Test that stderr from successful commands is forwarded to users.

    This tests the fix for silent failures where stderr messages (like warnings
    about multiple children) were captured but never shown to the user.
    """
    runner = CliRunner()
    # Use up command which can produce stderr on success
    # Test will simulate a command that succeeds but has no output
    result = runner.invoke(cli, ["__shell", "up"])
    # Result may fail or succeed, but stderr should be visible
    # The handler itself should forward stderr regardless of exit code
    assert result.exit_code in (0, 1)  # May fail due to missing config
    # If there's stderr, it should be captured
    # This test verifies the forwarding mechanism exists


def test_shell_integration_handles_multiline_output() -> None:
    """Test that handler doesn't crash on multi-line output from commands.

    This specifically tests the bug fix where consolidate --down would output
    multi-line success messages that caused Path operations to fail with
    'File name too long' errors.
    """
    runner = CliRunner()
    # consolidate --down may output multi-line messages without a script path
    result = runner.invoke(cli, ["__shell", "consolidate", "--down"])
    # Should handle gracefully without crashing
    assert result.exit_code in (0, 1)  # May fail due to missing worktrees
    # The critical test is that we don't get an OSError: File name too long


def test_shell_integration_handles_empty_stdout() -> None:
    """Test that handler correctly handles commands that produce no stdout.

    Some commands like 'consolidate --down' complete successfully but produce
    no activation script (stdout is empty). This should be handled gracefully.
    """
    runner = CliRunner()
    # Commands that might produce empty stdout
    result = runner.invoke(cli, ["__shell", "status"])
    # Should handle empty output gracefully
    assert result.exit_code in (0, 1)  # May fail due to missing config


def test_shell_integration_validates_script_path() -> None:
    """Test that handler validates output looks like a path before Path operations.

    The handler should check that output is actually path-like before attempting
    to create Path objects or check existence.
    """
    runner = CliRunner()
    # Use a command that might produce output
    result = runner.invoke(cli, ["__shell", "list"])
    # Should complete without Path-related errors
    assert result.exit_code in (0, 1)  # May fail due to missing config


# Tests for Click 8.2+ compatibility (no mix_stderr parameter)


def test_shell_integration_switch_invokes_successfully() -> None:
    """Test that __shell switch invokes command successfully without TypeError.

    This test verifies the fix for Click 8.2+ compatibility where mix_stderr=False
    was causing TypeError. The handler should successfully invoke commands using
    CliRunner without deprecated parameters.
    """
    from pathlib import Path

    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up multiple worktrees
        wt1_path = env.create_linked_worktree("feat-1", "feat-1", chdir=False)

        git_ops = FakeGit(
            git_common_dirs={
                env.cwd: env.git_dir,
                wt1_path: env.git_dir,
            },
            default_branches={
                env.cwd: "main",
                wt1_path: "feat-1",
            },
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                    WorktreeInfo(path=wt1_path, branch="feat-1", is_root=False),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        # Invoke switch command through __shell handler
        result = runner.invoke(cli, ["__shell", "switch", "feat-1"], obj=test_ctx)

        # The key test: No TypeError should occur (bug was mix_stderr=False causing TypeError)
        # Command may fail for other reasons (e.g., missing config), but should not crash
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"
        # If successful (exit 0), should return a script path
        # If failed (exit 1), may return passthrough marker
        if result.exit_code == 0 and result.stdout and result.stdout.strip():
            script_path_str = result.stdout.strip()
            # Verify it's a valid path (no TypeError occurred during invocation)
            if script_path_str and script_path_str != "__ERK_PASSTHROUGH__":
                script_path = Path(script_path_str)
                assert script_path.exists() or not script_path_str


def test_shell_integration_branch_checkout_invokes_successfully() -> None:
    """Test that __shell branch checkout invokes command successfully."""
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up worktree with branch
        wt1_path = env.create_linked_worktree("feat-1", "feat-1", chdir=False)

        git_ops = FakeGit(
            git_common_dirs={
                env.cwd: env.git_dir,
                wt1_path: env.git_dir,
            },
            default_branches={
                env.cwd: "main",
                wt1_path: "feat-1",
            },
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                    WorktreeInfo(path=wt1_path, branch="feat-1", is_root=False),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        result = runner.invoke(cli, ["__shell", "branch", "checkout", "feat-1"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons like missing config)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"


def test_shell_integration_up_invokes_successfully() -> None:
    """Test that __shell up invokes command successfully with worktree stack."""

    from erk_shared.gateway.graphite.types import BranchMetadata
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up stack: main → feat-1 → feat-2
        wt1_path = env.create_linked_worktree("feat-1", "feat-1", chdir=True)
        _wt2_path = env.create_linked_worktree("feat-2", "feat-2", chdir=False)

        git_ops, graphite_ops = env.build_ops_from_branches(
            {
                "main": BranchMetadata.trunk("main", children=["feat-1"]),
                "feat-1": BranchMetadata.branch("feat-1", "main", children=["feat-2"]),
                "feat-2": BranchMetadata.branch("feat-2", "feat-1"),
            },
            current_branch="feat-1",
            current_worktree=wt1_path,
        )

        test_ctx = env.build_context(git=git_ops, graphite=graphite_ops, use_graphite=True)

        result = runner.invoke(cli, ["__shell", "up"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons like missing config)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"


def test_shell_integration_down_invokes_successfully() -> None:
    """Test that __shell down invokes command successfully with worktree stack."""

    from erk_shared.gateway.graphite.types import BranchMetadata
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up stack: main → feat-1 → feat-2
        _wt1_path = env.create_linked_worktree("feat-1", "feat-1", chdir=False)
        wt2_path = env.create_linked_worktree("feat-2", "feat-2", chdir=True)

        git_ops, graphite_ops = env.build_ops_from_branches(
            {
                "main": BranchMetadata.trunk("main", children=["feat-1"]),
                "feat-1": BranchMetadata.branch("feat-1", "main", children=["feat-2"]),
                "feat-2": BranchMetadata.branch("feat-2", "feat-1"),
            },
            current_branch="feat-2",
            current_worktree=wt2_path,
        )

        test_ctx = env.build_context(git=git_ops, graphite=graphite_ops, use_graphite=True)

        result = runner.invoke(cli, ["__shell", "down"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons like missing config)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"


def test_shell_integration_create_invokes_successfully() -> None:
    """Test that __shell create invokes command successfully."""
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        git_ops = FakeGit(
            git_common_dirs={env.cwd: env.git_dir},
            default_branches={env.cwd: "main"},
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        result = runner.invoke(cli, ["__shell", "create", "newfeature"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons like missing config)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"


def test_shell_integration_consolidate_invokes_successfully() -> None:
    """Test that __shell consolidate invokes command successfully."""
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        git_ops = FakeGit(
            git_common_dirs={env.cwd: env.git_dir},
            default_branches={env.cwd: "main"},
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        result = runner.invoke(cli, ["__shell", "consolidate"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons or have no work to do)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"


def test_shell_handler_uses_stdout_not_output() -> None:
    """Test that handler extracts script path from stdout only, not mixed output.

    In Click 8.2+, result.stdout contains only stdout and result.stderr contains
    only stderr. The handler must use result.stdout for script path extraction
    to avoid mixing stderr messages with the script path.
    """
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up worktrees that might produce stderr output
        wt1_path = env.create_linked_worktree("feat-1", "feat-1", chdir=False)

        git_ops = FakeGit(
            git_common_dirs={
                env.cwd: env.git_dir,
                wt1_path: env.git_dir,
            },
            default_branches={
                env.cwd: "main",
                wt1_path: "feat-1",
            },
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                    WorktreeInfo(path=wt1_path, branch="feat-1", is_root=False),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        result = runner.invoke(cli, ["__shell", "switch", "feat-1"], obj=test_ctx)

        # Should succeed without TypeError (may fail for other reasons like missing config)
        assert result.exit_code in (0, 1), f"Unexpected exit code: {result.exit_code}"

        # The critical test: Verify stdout and stderr are separated properly
        # With Click 8.2+, result.stdout and result.stderr should exist independently
        # If we got this far without TypeError, the fix is working
        # stdout should contain only the script path or passthrough marker (or be empty)
        if result.stdout and result.stdout.strip():
            script_path_str = result.stdout.strip()
            # Should be a single line (path or marker), not mixed with stderr
            # This verifies we're using result.stdout, not result.output
            assert "\n\n" not in script_path_str  # No multi-paragraph content mixing


def test_shell_integration_shows_note_for_no_directory_change() -> None:
    """Test that handler shows helpful message when command succeeds but produces no script.

    When a command completes successfully (exit_code=0) but produces no activation
    script (empty stdout), the handler should display a note explaining that no
    directory change is needed. This clarifies expected behavior for users.
    """
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_inmem_env

    runner = CliRunner()
    with erk_inmem_env(runner) as env:
        # Set up minimal environment - consolidate with no worktrees to remove
        git_ops = FakeGit(
            git_common_dirs={env.cwd: env.git_dir},
            default_branches={env.cwd: "main"},
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                ]
            },
        )

        test_ctx = env.build_context(git=git_ops)

        # Use consolidate command - when there's nothing to consolidate, it succeeds
        # but produces no script output (no directory change needed)
        result = runner.invoke(cli, ["__shell", "consolidate"], obj=test_ctx)

        # Command should succeed with no work to do
        if result.exit_code == 0:
            # Verify the helpful note is displayed
            assert "completed (no directory change needed)" in result.output


def test_shell_integration_create_from_current_branch_returns_script_path() -> None:
    """Test that create --from-current-branch through __shell returns script path in stdout.

    This test verifies the bug fix where create --from-current-branch was writing
    the script path to stderr via user_output() instead of stdout via machine_output().
    The handler reads result.stdout to get the script path - if stdout is empty,
    the handler displays "no directory change needed" instead of switching directories.

    See: https://github.com/anthropics/erk/issues/XXX
    """
    from erk_shared.git.abc import WorktreeInfo
    from erk_shared.git.fake import FakeGit
    from tests.test_utils.env_helpers import erk_isolated_fs_env

    runner = CliRunner()
    with erk_isolated_fs_env(runner) as env:
        # Set up git state: in root worktree on feature branch
        git_ops = FakeGit(
            worktrees={
                env.cwd: [
                    WorktreeInfo(path=env.cwd, branch="main", is_root=True),
                ]
            },
            current_branches={env.cwd: "my-feature"},
            default_branches={env.cwd: "main"},
            git_common_dirs={env.cwd: env.git_dir},
        )

        test_ctx = env.build_context(git=git_ops)

        # Act: Create worktree from current branch through __shell handler
        result = runner.invoke(
            cli,
            ["__shell", "create", "--from-current-branch"],
            obj=test_ctx,
            catch_exceptions=False,
        )

        # Handler creates its own context, so command may fail for various reasons
        # (missing config, filesystem setup, etc.). That's OK - the key test is:
        # IF the command succeeds, does it output to stdout?
        if result.exit_code == 0:
            # Assert: Handler received script path in stdout (not stderr)
            # Before fix: stdout is empty, handler shows "no directory change needed"
            # After fix: stdout contains script path, handler processes it
            assert result.stdout.strip() != "", (
                "Handler should receive script path in stdout. "
                "If stdout is empty, handler shows 'no directory change needed' "
                "instead of switching to the new worktree."
            )

            # Assert: The "no directory change needed" message should NOT appear
            assert "no directory change needed" not in result.output, (
                "Handler should not show 'no directory change needed' because "
                "create command should produce a script path for directory switching."
            )

            # Assert: Script path points to a valid activation script
            script_path_str = result.stdout.strip()
            # Verify it's not the passthrough marker
            assert script_path_str != "__ERK_PASSTHROUGH__", (
                "Should not passthrough - command should generate script"
            )
            script_path = Path(script_path_str)
            script_content = env.script_writer.get_script_content(script_path)
            assert script_content is not None, "Handler should receive a valid script path"
        else:
            # Command failed, which means handler returned passthrough or error
            # This is acceptable for testing purposes - the command-level test
            # already verifies the fix works when the command succeeds
            assert result.exit_code == 1, f"Unexpected exit code: {result.exit_code}"
