from __future__ import annotations

import asyncio
import builtins
import importlib
import subprocess
from pathlib import Path

import pytest

from reddit_mcp import cli as cli_module
from reddit_mcp import config as config_module
from reddit_mcp import cookie_manager as cookie_manager_module
from reddit_mcp import uninstall as uninstall_module


@pytest.mark.anyio
async def test_setup_uses_register_phase_and_skill_install(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    class FakeTokenManager:
        def __init__(self, *_args: object, **_kwargs: object) -> None:
            pass

        async def get_valid_token(self) -> str:
            return "pat_ready"

    cookie_calls: dict[str, bool] = {}
    register_calls: dict[str, bool] = {}
    shutdown_flags: list[bool] = []

    async def fake_setup_cookies(*_args: object, **kwargs: object) -> dict[str, str]:
        cookie_calls["called"] = True
        shutdown_flags.append(bool(kwargs.get("shutdown_browser")))
        return {"reddit_session": "ok"}

    def fake_register_phase() -> None:
        register_calls["called"] = True

    monkeypatch.setattr(cli_module, "TokenManager", FakeTokenManager)
    monkeypatch.setattr("reddit_mcp.cookie_manager.setup_cookies", fake_setup_cookies)
    monkeypatch.setattr(cli_module, "_run_register_phase", fake_register_phase, raising=False)

    await cli_module._async_setup(shutdown_browser=True)

    assert cookie_calls.get("called") is True
    assert shutdown_flags == [True]
    assert register_calls.get("called") is True


def test_help_contains_install_skills_command(capsys: pytest.CaptureFixture[str]) -> None:
    cli_module._print_help()
    out = capsys.readouterr().out
    assert "reddit-mcp install-skills" in out


def test_help_uses_canonical_setup_contract(capsys: pytest.CaptureFixture[str]) -> None:
    cli_module._print_help()
    out = capsys.readouterr().out

    assert "reddit-mcp serve" in out
    assert "reddit-mcp setup" in out
    assert "bootstrap" not in out
    assert "uvx --from" not in out
    assert '"command": "python3"' not in out


def test_readme_documents_uv_first_contract() -> None:
    readme_path = Path(__file__).resolve().parents[1] / "README.md"
    readme = readme_path.read_text(encoding="utf-8")

    assert "uv tool install 5mghost-rover" in readme
    assert "reddit-mcp setup" in readme
    assert "reddit-mcp serve" in readme
    assert "uv tool run --from 5mghost-rover playwright install chromium" in readme
    assert "launcher.py" not in readme
    assert "uvx --from 5mghost-rover reddit-mcp serve" not in readme
    assert "pip install playwright" not in readme


def test_config_module_no_longer_exports_launcher_paths() -> None:
    assert not hasattr(config_module, "LAUNCHER_PATH")
    assert not hasattr(config_module, "LEGACY_LAUNCHER_PATH")


def test_uninstall_uses_shared_unregister(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    cfg_dir = tmp_path / ".mkterswingman" / "reddit-mcp"
    legacy_dir = tmp_path / ".reddit-mcp"
    cfg_dir.mkdir(parents=True, exist_ok=True)
    legacy_dir.mkdir(parents=True, exist_ok=True)

    calls: dict[str, object] = {}

    def fake_unregister_all(*, server_name: str) -> None:
        calls["server_name"] = server_name

    monkeypatch.setattr(uninstall_module, "CONFIG_DIR", cfg_dir)
    monkeypatch.setattr(uninstall_module, "LEGACY_CONFIG_DIR", legacy_dir)
    monkeypatch.setattr(uninstall_module, "unregister_all", fake_unregister_all, raising=False)
    removed: list[Path] = []
    monkeypatch.setattr(uninstall_module.shutil, "rmtree", lambda path: removed.append(path))

    uninstall_module.run_uninstall()

    assert calls.get("server_name") == "reddit-mcp"
    assert removed == [cfg_dir, legacy_dir]


def test_bundled_skill_is_packaged_in_source_tree() -> None:
    skill_path = (
        Path(__file__).resolve().parents[1]
        / "src"
        / "reddit_mcp"
        / "skills"
        / "use-reddit-mcp"
        / "SKILL.md"
    )
    assert skill_path.exists()


@pytest.mark.anyio
async def test_setup_cookies_command_passes_shutdown_browser_flag(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: list[bool] = []

    async def fake_setup_cookies(*_args: object, **kwargs: object) -> dict[str, str]:
        seen.append(bool(kwargs.get("shutdown_browser")))
        return {"reddit_session": "ok"}

    monkeypatch.setattr("reddit_mcp.cookie_manager.setup_cookies", fake_setup_cookies)

    await cli_module._async_setup_cookies(shutdown_browser=True)

    assert seen == [True]


def test_register_phase_surfaces_registration_failure(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        cli_module,
        "register_all",
        lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("target_client=codex config_path=/tmp/codex manual_command='reddit-mcp serve'")),
    )

    with pytest.raises(RuntimeError, match="reddit-mcp serve"):
        cli_module._run_register_phase()


def test_install_bundled_skills_includes_openclaw(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: dict[str, object] = {}

    def fake_install_skills(
        *,
        skill_name: str,
        source_dir: Path,
        include_openclaw: bool,
    ) -> None:
        seen["skill_name"] = skill_name
        seen["source_dir"] = source_dir
        seen["include_openclaw"] = include_openclaw

    monkeypatch.setattr(cli_module, "install_skills", fake_install_skills)

    cli_module._install_bundled_skills()

    assert seen["skill_name"] == "use-reddit-mcp"
    assert isinstance(seen["source_dir"], Path)
    assert seen["include_openclaw"] is True


@pytest.mark.anyio
async def test_server_setup_guidance_uses_canonical_commands() -> None:
    server_module = importlib.reload(importlib.import_module("reddit_mcp.server"))
    server_module._register_setup_required()

    result = await server_module.mcp.call_tool("setup_required", {})
    payload = result.structured_content["result"]

    assert '"command": "reddit-mcp"' in payload
    assert '"args": ["serve"]' in payload
    assert "REDDIT_MCP_TOKEN" in payload
    assert "reddit-mcp setup-cookies" in payload
    assert "uvx --from" not in payload


@pytest.mark.anyio
async def test_server_cookie_guidance_uses_canonical_commands() -> None:
    server_module = importlib.reload(importlib.import_module("reddit_mcp.server"))
    server_module._register_setup_cookies_required()

    result = await server_module.mcp.call_tool("setup_cookies_required", {})
    payload = result.structured_content["result"]

    assert "Reddit cookies are missing" in payload
    assert "reddit-mcp setup-cookies" in payload
    assert "uvx --from" not in payload


@pytest.mark.anyio
async def test_verify_phase_fails_without_shared_auth(monkeypatch: pytest.MonkeyPatch) -> None:
    class FakeTokenManager:
        async def get_valid_token(self) -> str | None:
            return None

    monkeypatch.setattr("reddit_mcp.cookie_manager.load_cookies", lambda: {"reddit_session": "ok"})

    with pytest.raises(SystemExit) as excinfo:
        await cli_module._run_verify_phase(FakeTokenManager())

    assert excinfo.value.code == 1


@pytest.mark.anyio
async def test_verify_phase_fails_without_reddit_cookies(monkeypatch: pytest.MonkeyPatch) -> None:
    class FakeTokenManager:
        async def get_valid_token(self) -> str | None:
            return "pat_ready"

    monkeypatch.setattr("reddit_mcp.cookie_manager.load_cookies", lambda: None)

    with pytest.raises(SystemExit) as excinfo:
        await cli_module._run_verify_phase(FakeTokenManager())

    assert excinfo.value.code == 1


def test_update_shows_windows_lock_recovery_guidance(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    def fake_run(*args, **kwargs):  # type: ignore[no-untyped-def]
        cmd = args[0]
        if cmd[:4] == ["uv", "tool", "upgrade", "5mghost-rover"]:
            raise subprocess.CalledProcessError(
                1,
                cmd,
                stderr=(
                    "ERROR: Could not install packages due to an OSError: "
                    "[WinError 32] 另一个程序正在使用此文件，进程无法访问。: "
                    "'c:\\program files\\python314\\scripts\\reddit-mcp.exe'"
                ),
            )
        raise AssertionError(f"Unexpected subprocess.run call: {cmd}")

    monkeypatch.setattr(cli_module.sys, "platform", "win32")
    monkeypatch.setattr(cli_module, "_resolve_uv_command", lambda: ["uv"])
    monkeypatch.setattr(cli_module.subprocess, "run", fake_run)

    with pytest.raises(SystemExit) as excinfo:
        cli_module._cmd_update()

    assert excinfo.value.code == 1
    out = capsys.readouterr().out
    assert "another program is still using it" in out
    assert "Close OpenClaw, Codex, Claude, Gemini" in out
    assert "uv tool install --upgrade 5mghost-rover" in out


def test_update_uses_python_explicit_manual_command_on_timeout(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    monkeypatch.setattr(cli_module, "_resolve_uv_command", lambda: ["uv"])
    monkeypatch.setattr(
        cli_module.subprocess,
        "run",
        lambda *args, **kwargs: (_ for _ in ()).throw(subprocess.TimeoutExpired("uv", 15)),
    )

    with pytest.raises(SystemExit) as excinfo:
        cli_module._cmd_update()

    assert excinfo.value.code == 1
    out = capsys.readouterr().out
    assert "uv tool install --upgrade 5mghost-rover" in out


def test_update_uses_uv_tool_upgrade_command(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: list[list[str]] = []

    def fake_run(cmd, **kwargs):  # type: ignore[no-untyped-def]
        seen.append(cmd)
        return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")

    monkeypatch.setattr(cli_module, "_resolve_uv_command", lambda: ["uv"])
    monkeypatch.setattr(cli_module.subprocess, "run", fake_run)
    monkeypatch.setattr(cli_module, "_install_bundled_skills", lambda: None)

    cli_module._cmd_update()

    assert seen == [["uv", "tool", "upgrade", "5mghost-rover"]]


@pytest.mark.anyio
async def test_cookie_setup_missing_playwright_uses_uv_guidance(monkeypatch: pytest.MonkeyPatch) -> None:
    original_import = builtins.__import__

    def fake_import(name, globals=None, locals=None, fromlist=(), level=0):  # type: ignore[no-untyped-def]
        if name == "playwright.async_api":
            raise ImportError("playwright missing")
        return original_import(name, globals, locals, fromlist, level)

    monkeypatch.setattr(builtins, "__import__", fake_import)

    with pytest.raises(RuntimeError) as excinfo:
        await cookie_manager_module._collect_reddit_cookies_via_playwright(silent=True)

    message = str(excinfo.value)
    assert "uv tool run --from 5mghost-rover playwright install chromium" in message
    assert "pip install playwright" not in message
