from __future__ import annotations

import argparse
from collections.abc import Callable
from pathlib import Path

import pytest

from tests.conftest import build_test_vibe_config
from vibe.cli import cli as cli_mod, entrypoint as entrypoint_mod
from vibe.core.config import MissingAPIKeyError
from vibe.core.trusted_folders import trusted_folders_manager


def _make_args(**overrides: object) -> argparse.Namespace:
    base: dict[str, object] = {
        "initial_prompt": None,
        "prompt": "hello",
        "max_turns": None,
        "max_price": None,
        "max_tokens": None,
        "enabled_tools": None,
        "output": "text",
        "agent": "default",
        "check_upgrade": False,
        "setup": False,
        "workdir": None,
        "add_dir": [],
        "trust": False,
        "teleport": False,
        "continue_session": False,
        "resume": None,
    }
    base.update(overrides)
    return argparse.Namespace(**base)


def test_programmatic_mode_does_not_run_onboarding_on_missing_api_key(
    monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    def boom() -> None:
        raise MissingAPIKeyError("MISTRAL_API_KEY", "mistral")

    monkeypatch.setattr(cli_mod.VibeConfig, "load", staticmethod(boom))

    sentinel: dict[str, bool] = {"called": False}

    def fail_onboarding(*_args: object, **_kwargs: object) -> None:
        sentinel["called"] = True

    monkeypatch.setattr(cli_mod, "run_onboarding", fail_onboarding)

    with pytest.raises(SystemExit) as exc_info:
        cli_mod.load_config_or_exit(interactive=False)

    assert exc_info.value.code == 1
    assert sentinel["called"] is False
    err = capsys.readouterr().err
    assert "MISTRAL_API_KEY" in err
    assert "vibe --setup" in err


def test_interactive_mode_still_runs_onboarding_on_missing_api_key(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    # Replace VibeConfig.load with a stub that fails the first time.
    state = {"raised": False}

    def fake_load() -> object:
        if not state["raised"]:
            state["raised"] = True
            raise MissingAPIKeyError("MISTRAL_API_KEY", "mistral")
        return "config-sentinel"

    monkeypatch.setattr(cli_mod.VibeConfig, "load", staticmethod(fake_load))

    onboarding_called: list[bool] = []
    monkeypatch.setattr(
        cli_mod, "run_onboarding", lambda *a, **k: onboarding_called.append(True)
    )

    result = cli_mod.load_config_or_exit(interactive=True)
    assert onboarding_called == [True]
    assert result == "config-sentinel"


def test_warn_if_workdir_untrusted_writes_stderr_when_project_config_present(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    (project / "AGENTS.md").write_text("hello", encoding="utf-8")
    monkeypatch.chdir(project)

    cli_mod.warn_if_workdir_trust_is_unset()

    err = " ".join(capsys.readouterr().err.split())
    assert "not trusted" in err
    assert "AGENTS.md" in err
    assert "--trust" in err


def test_warn_if_workdir_untrusted_silent_when_already_trusted(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    (project / "AGENTS.md").write_text("hello", encoding="utf-8")
    monkeypatch.chdir(project)

    trusted_folders_manager.add_trusted(project)

    cli_mod.warn_if_workdir_trust_is_unset()

    assert capsys.readouterr().err == ""


def test_warn_if_workdir_untrusted_silent_when_no_project_config(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    monkeypatch.chdir(project)

    cli_mod.warn_if_workdir_trust_is_unset()

    assert capsys.readouterr().err == ""


def test_trust_flag_trusts_cwd_for_session_only(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    monkeypatch.chdir(project)

    args = _make_args(trust=True, prompt=None)
    monkeypatch.setattr(entrypoint_mod, "parse_arguments", lambda: args)
    monkeypatch.setattr(
        entrypoint_mod, "init_harness_files_manager", lambda *a, **k: None
    )

    # Stop main() before it runs the actual CLI.
    def fake_run_cli(_args: argparse.Namespace, **_kwargs: object) -> None:
        raise SystemExit(0)

    monkeypatch.setattr("vibe.cli.cli.run_cli", fake_run_cli)

    with pytest.raises(SystemExit) as exc_info:
        entrypoint_mod.main()
    assert exc_info.value.code == 0

    assert trusted_folders_manager.is_trusted(project) is True
    # --trust must NOT persist to trusted_folders.toml.
    assert trusted_folders_manager._trusted == []
    assert str(project.resolve()) in trusted_folders_manager._session_trusted


def test_trust_flag_works_in_programmatic_mode(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    monkeypatch.chdir(project)

    args = _make_args(trust=True, prompt="run")
    monkeypatch.setattr(entrypoint_mod, "parse_arguments", lambda: args)
    monkeypatch.setattr(
        entrypoint_mod,
        "check_and_resolve_trusted_folder",
        lambda _cwd: pytest.fail("must not prompt in -p mode"),
    )
    monkeypatch.setattr(
        entrypoint_mod, "init_harness_files_manager", lambda *a, **k: None
    )

    def fake_run_cli(_args: argparse.Namespace, **_kwargs: object) -> None:
        raise SystemExit(0)

    monkeypatch.setattr("vibe.cli.cli.run_cli", fake_run_cli)

    with pytest.raises(SystemExit):
        entrypoint_mod.main()

    assert trusted_folders_manager.is_trusted(project) is True
    assert trusted_folders_manager._trusted == []


def test_check_upgrade_does_not_pass_trust_resolver(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    (project / "AGENTS.md").write_text("hello", encoding="utf-8")
    monkeypatch.chdir(project)

    args = _make_args(prompt=None, check_upgrade=True)
    monkeypatch.setattr(entrypoint_mod, "parse_arguments", lambda: args)
    monkeypatch.setattr(
        entrypoint_mod,
        "check_and_resolve_trusted_folder",
        lambda _cwd: pytest.fail("check-upgrade must not prompt for trust"),
    )
    monkeypatch.setattr(
        entrypoint_mod, "init_harness_files_manager", lambda *a, **k: None
    )

    def fake_run_cli(
        _args: argparse.Namespace,
        *,
        resolve_trusted_folder: Callable[[], None] | None = None,
    ) -> None:
        assert resolve_trusted_folder is None
        raise SystemExit(0)

    monkeypatch.setattr("vibe.cli.cli.run_cli", fake_run_cli)

    with pytest.raises(SystemExit) as exc_info:
        entrypoint_mod.main()

    assert exc_info.value.code == 0


def test_interactive_start_passes_trust_resolver_to_cli(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    project = tmp_path / "proj"
    project.mkdir()
    monkeypatch.chdir(project)

    args = _make_args(prompt=None)
    calls: list[str] = []
    monkeypatch.setattr(entrypoint_mod, "parse_arguments", lambda: args)
    monkeypatch.setattr(
        entrypoint_mod,
        "check_and_resolve_trusted_folder",
        lambda _cwd: calls.append("trust"),
    )
    monkeypatch.setattr(
        entrypoint_mod, "init_harness_files_manager", lambda *a, **k: None
    )

    def fake_run_cli(
        _args: argparse.Namespace,
        *,
        resolve_trusted_folder: Callable[[], None] | None = None,
    ) -> None:
        assert callable(resolve_trusted_folder)
        resolve_trusted_folder()
        raise SystemExit(0)

    monkeypatch.setattr("vibe.cli.cli.run_cli", fake_run_cli)

    with pytest.raises(SystemExit) as exc_info:
        entrypoint_mod.main()

    assert exc_info.value.code == 0
    assert calls == ["trust"]


def test_session_trust_does_not_write_to_disk(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    trust_file = tmp_path / "trusted_folders.toml"
    monkeypatch.setattr(trusted_folders_manager, "_file_path", trust_file)
    project = tmp_path / "proj"
    project.mkdir()

    trusted_folders_manager.trust_for_session(project)

    assert trusted_folders_manager.is_trusted(project) is True
    assert not trust_file.exists()


def test_run_cli_passes_max_tokens_to_run_programmatic(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    args = _make_args(max_tokens=123)
    call: dict[str, object] = {}
    config = build_test_vibe_config()

    monkeypatch.setattr(cli_mod, "bootstrap_config_files", lambda: None)
    monkeypatch.setattr(cli_mod, "load_config_or_exit", lambda interactive: config)
    monkeypatch.setattr(cli_mod, "load_hooks_from_fs", lambda _config: None)
    monkeypatch.setattr(cli_mod, "setup_tracing", lambda _config: None)
    monkeypatch.setattr(cli_mod, "load_session", lambda _args, _config: None)
    monkeypatch.setattr(cli_mod, "get_prompt_from_stdin", lambda: None)
    monkeypatch.setattr(cli_mod, "warn_if_workdir_trust_is_unset", lambda: None)
    monkeypatch.setattr(cli_mod, "get_initial_agent_name", lambda _args, _config: "x")

    def fake_run_programmatic(**kwargs: object) -> str:
        call.update(kwargs)
        return "done"

    monkeypatch.setattr(cli_mod, "run_programmatic", fake_run_programmatic)

    with pytest.raises(SystemExit) as exc_info:
        cli_mod.run_cli(args)

    assert exc_info.value.code == 0
    assert call["max_session_tokens"] == 123


def test_run_cli_runs_update_prompt_before_trust_resolver(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    args = _make_args(prompt=None)
    config = build_test_vibe_config()
    calls: list[str] = []

    monkeypatch.setattr(cli_mod, "bootstrap_config_files", lambda: None)
    monkeypatch.setattr(cli_mod, "load_config_or_exit", lambda interactive: config)
    monkeypatch.setattr(
        cli_mod,
        "_maybe_run_startup_update_prompt",
        lambda _config, _repository: calls.append("update"),
    )

    def resolve_trusted_folder() -> None:
        calls.append("trust")
        raise SystemExit(0)

    with pytest.raises(SystemExit) as exc_info:
        cli_mod.run_cli(args, resolve_trusted_folder=resolve_trusted_folder)

    assert exc_info.value.code == 0
    assert calls == ["update", "trust"]


def test_run_cli_check_upgrade_exits_before_loading_config(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    args = _make_args(prompt=None, check_upgrade=True)
    call: dict[str, object] = {}

    monkeypatch.setattr(cli_mod, "bootstrap_config_files", lambda: None)
    monkeypatch.setattr(
        cli_mod,
        "load_config_or_exit",
        lambda interactive: pytest.fail("check-upgrade should not load config"),
    )
    monkeypatch.setattr(cli_mod, "load_update_prompt_theme", lambda: "dracula")

    def fake_run_check_upgrade(_repository: object, *, theme: str | None) -> None:
        call["theme"] = theme

    monkeypatch.setattr(cli_mod, "_run_check_upgrade", fake_run_check_upgrade)

    with pytest.raises(SystemExit) as exc_info:
        cli_mod.run_cli(
            args,
            resolve_trusted_folder=lambda: pytest.fail(
                "check-upgrade should not prompt for trust"
            ),
        )

    assert exc_info.value.code == 0
    assert call["theme"] == "dracula"
