import json
import sys
from pathlib import Path

from click.testing import CliRunner

from dont_hallucinate import cli
from dont_hallucinate.cli import main


def test_activate_powershell_uses_workspace_local_stream(tmp_path: Path) -> None:
    runner = CliRunner()
    result = runner.invoke(main, ["activate", "--shell", "powershell", "--workspace", str(tmp_path)])

    assert result.exit_code == 0
    assert str(tmp_path) in result.output
    assert str(tmp_path / ".hallucinate" / "stream.jsonl") in result.output
    assert "hook.ps1" in result.output
    assert "\n" not in result.output.strip()
    assert "$env:HALLUCINATE_ACTOR = 'human'" in result.output


def test_activate_agent_bash_sets_bash_env(tmp_path: Path) -> None:
    runner = CliRunner()
    result = runner.invoke(main, ["activate", "--shell", "agent-bash", "--workspace", str(tmp_path)])

    assert result.exit_code == 0
    assert "export HALLUCINATE_ACTOR='agent'" in result.output
    assert "export BASH_ENV=" in result.output
    assert "export PATH=" in result.output
    assert "hook_noninteractive.sh" in result.output
    assert "\\" not in result.output


def test_shell_powershell_spawns_hooked_process(monkeypatch, tmp_path: Path) -> None:
    runner = CliRunner()
    recorded: dict[str, object] = {}

    monkeypatch.setattr(cli.shutil, "which", lambda name: "powershell.exe" if name == "powershell" else None)

    def fake_call(args, env=None, cwd=None):
        recorded["args"] = args
        recorded["env"] = env
        recorded["cwd"] = cwd
        return 0

    monkeypatch.setattr(cli.subprocess, "call", fake_call)
    result = runner.invoke(main, ["shell", "--workspace", str(tmp_path), "--actor", "agent"])

    assert result.exit_code == 0
    assert recorded["args"][0] == "powershell.exe"
    assert recorded["env"]["HALLUCINATE_ACTOR"] == "agent"
    assert recorded["env"]["HALLUCINATE_STREAM_FILE"].endswith(".hallucinate\\stream.jsonl")


def test_shell_bash_requires_binary(monkeypatch, tmp_path: Path) -> None:
    runner = CliRunner()
    monkeypatch.setattr(cli.shutil, "which", lambda name: None)
    result = runner.invoke(main, ["shell", "--shell", "bash", "--workspace", str(tmp_path)])
    assert result.exit_code != 0
    assert "bash is not available on PATH." in result.output


def test_bash_command_wraps_real_bash(monkeypatch, tmp_path: Path) -> None:
    runner = CliRunner()
    recorded: dict[str, object] = {}

    monkeypatch.setattr(cli, "_real_bash_path", lambda: "C:\\real\\bash.exe")

    def fake_wrap(workspace, actor, command):
        recorded["workspace"] = workspace
        recorded["actor"] = actor
        recorded["command"] = command
        raise SystemExit(0)

    monkeypatch.setattr(cli, "_run_wrapped_command", fake_wrap)
    result = runner.invoke(main, ["bash", "--workspace", str(tmp_path), "--actor", "agent", "--", "-lc", "badcmd"])

    assert result.exit_code == 0
    assert recorded["actor"] == "agent"
    assert recorded["command"][0] == "C:\\real\\bash.exe"
    assert recorded["command"][1:] == ("-lc", "badcmd")


def test_activate_powershell_mentions_shim_path(tmp_path: Path) -> None:
    runner = CliRunner()
    result = runner.invoke(main, ["activate", "--shell", "powershell", "--workspace", str(tmp_path)])

    assert result.exit_code == 0
    assert ".hallucinate\\bin" in result.output


def test_exec_logs_and_decorates_failures(tmp_path: Path) -> None:
    runner = CliRunner()
    result = runner.invoke(
        main,
        [
            "exec",
            "--workspace",
            str(tmp_path),
            "--actor",
            "agent",
            sys.executable,
            "-c",
            "import sys; sys.stderr.write('boom\\n'); sys.exit(3)",
        ],
    )

    assert result.exit_code == 3
    assert "[dont-hallucinate/agent] caught:" in result.output
    assert "[dont-hallucinate/agent] roast:" in result.output

    stream = tmp_path / ".hallucinate" / "stream.jsonl"
    payload = json.loads(stream.read_text(encoding="utf-8").strip())
    assert payload["actor"] == "agent"
    assert payload["exit_code"] == 3


def test_exec_preserves_stdout_on_success(tmp_path: Path) -> None:
    runner = CliRunner()
    result = runner.invoke(
        main,
        [
            "exec",
            "--workspace",
            str(tmp_path),
            sys.executable,
            "-c",
            "print('ok')",
        ],
    )

    assert result.exit_code == 0
    assert "ok" in result.output


def test_explain_renders_failure_report(tmp_path: Path) -> None:
    runner = CliRunner()
    stderr_file = tmp_path / "stderr.txt"
    stderr_file.write_text("npm error Missing script: \"test\"\n", encoding="utf-8")

    result = runner.invoke(
        main,
        [
            "explain",
            "--actor",
            "agent",
            "--command",
            "npm test",
            "--exit-code",
            "1",
            "--stderr-file",
            str(stderr_file),
        ],
    )

    assert result.exit_code == 0
    assert "[dont-hallucinate/agent] caught: npm test" in result.output
    assert "[dont-hallucinate/agent] roast:" in result.output


def test_install_hook_codex_project_writes_shell_environment_policy(tmp_path: Path, monkeypatch) -> None:
    runner = CliRunner()
    config_path = tmp_path / ".codex" / "config.toml"
    config_path.parent.mkdir(parents=True, exist_ok=True)
    config_path.write_text('model = "gpt-5.4"\n', encoding="utf-8")

    monkeypatch.setattr(cli, "_codex_config_path", lambda global_install: config_path)
    monkeypatch.setattr(cli, "_agent_stream_file", lambda agent_name: tmp_path / f"{agent_name}-stream.jsonl")

    result = runner.invoke(main, ["install-hook-codex", "--project"])

    assert result.exit_code == 0
    content = config_path.read_text(encoding="utf-8")
    assert '[shell_environment_policy]' in content
    assert 'inherit = "all"' in content
    assert '[shell_environment_policy.set]' in content
    assert 'BASH_ENV = ' in content
    assert 'HALLUCINATE_ACTOR = "agent"' in content


def test_install_hook_cursor_writes_guarded_profile_snippets(tmp_path: Path, monkeypatch) -> None:
    runner = CliRunner()
    ps_profile = tmp_path / "Microsoft.PowerShell_profile.ps1"
    bash_profile = tmp_path / ".bashrc"
    zsh_profile = tmp_path / ".zshrc"

    monkeypatch.setattr(cli, "_cursor_powershell_profile", lambda: ps_profile)
    monkeypatch.setattr(cli, "_cursor_shell_profiles", lambda: [bash_profile, zsh_profile])
    monkeypatch.setattr(cli, "_agent_stream_file", lambda agent_name: tmp_path / f"{agent_name}-stream.jsonl")

    result = runner.invoke(main, ["install-hook-cursor"])

    assert result.exit_code == 0
    assert "CURSOR_AGENT" in ps_profile.read_text(encoding="utf-8")
    assert "hook.ps1" in ps_profile.read_text(encoding="utf-8")
    assert "CURSOR_AGENT" in bash_profile.read_text(encoding="utf-8")
    assert "hook.sh" in bash_profile.read_text(encoding="utf-8")
    assert "CURSOR_AGENT" in zsh_profile.read_text(encoding="utf-8")
