from __future__ import annotations

from pathlib import Path
import sys

from typer.testing import CliRunner

from zsc import __version__ as zsc_version
from zsc.cli import app


runner = CliRunner()


def _write_task_md(path: Path, title: str, completed: bool) -> None:
    status_block = (
        "> 只维护最新版本；完成后清空 TODO，仅保留\"完成记录 + 日期\"。\n"
        "- （本轮已完成，TODO 清空）\n"
    )
    todos = "" if completed else "- [ ] TODO item 1\n"
    path.write_text(
        f"# {title}\n\n"
        "## 闭环描述\n\n"
        "...\n\n"
        "## TODO_LIST\n\n"
        f"{status_block}\n"
        f"{todos}",
        encoding="utf-8",
    )


def test_task_list_on_empty_agents(tmp_path: Path) -> None:
    result = runner.invoke(app, ["task", "list", str(tmp_path)])
    assert result.exit_code == 0
    assert "No tasks found" in result.stdout


def test_task_new_creates_task_structure(tmp_path: Path) -> None:
    result = runner.invoke(app, ["task", "new", "demo_feature", str(tmp_path)])
    assert result.exit_code == 0
    # Guidance about using zsc-create-task and LLM limitation should be printed
    assert "zsc-create-task" in result.stdout
    assert "仅负责创建目录与模板" in result.stdout

    task_dir = tmp_path / ".agents" / "tasks" / "task_01_demo_feature"
    assert task_dir.exists()
    assert (task_dir / "task_01_demo_feature.md").exists()
    assert (task_dir / "task_records" / "log").exists()

    # list should now show this task as open (has TODO items)
    list_result = runner.invoke(app, ["task", "list", str(tmp_path)])
    assert list_result.exit_code == 0
    assert "task_01_demo_feature" in list_result.stdout


def test_task_status_counts_completed_and_open(tmp_path: Path) -> None:
    tasks_root = tmp_path / ".agents" / "tasks"
    (tasks_root / "task_01_a").mkdir(parents=True)
    (tasks_root / "task_02_b").mkdir(parents=True)

    _write_task_md(tasks_root / "task_01_a" / "task_01_a.md", "Task 01: A", completed=True)
    _write_task_md(tasks_root / "task_02_b" / "task_02_b.md", "Task 02: B", completed=False)

    result = runner.invoke(app, ["task", "status", str(tmp_path)])
    assert result.exit_code == 0
    # basic sanity checks on counts
    assert "total:" in result.stdout
    assert "completed: 1" in result.stdout
    assert "open:      1" in result.stdout


def test_parse_task_md_prefers_named_md_fallback_to_task_md(tmp_path: Path) -> None:
    """Legacy task.md is still read when task_dir_name.md does not exist."""
    tasks_root = tmp_path / ".agents" / "tasks"
    tasks_root.mkdir(parents=True)
    (tasks_root / "task_01_legacy").mkdir()
    _write_task_md(tasks_root / "task_01_legacy" / "task.md", "Task 01: Legacy", completed=True)
    result = runner.invoke(app, ["task", "list", str(tmp_path)])
    assert result.exit_code == 0
    assert "task_01_legacy" in result.stdout
    assert "completed" in result.stdout


def test_root_without_args_shows_purpose_and_usage() -> None:
    result = runner.invoke(app, [])
    assert result.exit_code == 0
    assert f"zsc {zsc_version}: 初始化并管理基于 .agents/tasks 的 AI Agents 任务体系。" in result.stdout
    assert "zsc init ." in result.stdout
    assert "zsc task list" in result.stdout


def test_version_option_short_flag() -> None:
    result = runner.invoke(app, ["-V"])
    assert result.exit_code == 0
    assert f"zsc {zsc_version}" in result.stdout


def test_version_option_long_flag() -> None:
    result = runner.invoke(app, ["--version"])
    assert result.exit_code == 0
    assert f"zsc {zsc_version}" in result.stdout


def test_help_shows_version() -> None:
    result = runner.invoke(app, ["--help"])
    assert result.exit_code == 0
    assert zsc_version in result.stdout


def test_upgrade_prefers_uv_tool_when_available(monkeypatch, tmp_path: Path) -> None:
    """When uv is available and .venv is healthy, zsc -U should use uv tool install."""
    from zsc import cli as cli_mod

    # Simulate running in a temporary project directory
    monkeypatch.setattr(cli_mod.Path, "cwd", classmethod(lambda cls: tmp_path))

    # uv is available
    monkeypatch.setattr(cli_mod.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None)

    calls: list[list[str]] = []

    class Result:
        def __init__(self, returncode: int) -> None:
            self.returncode = returncode

    def fake_run(cmd, check=False):  # type: ignore[no-untyped-def]
        calls.append(cmd)
        return Result(0)

    monkeypatch.setattr(cli_mod.subprocess, "run", fake_run)

    result = runner.invoke(app, ["-U"])
    assert result.exit_code == 0
    assert ["uv", "tool", "install", "zsc", "--upgrade"] in calls
    assert "[zsc] Upgrading zsc to latest version..." in result.stdout
    assert "Upgrade finished" in result.stdout


def test_upgrade_skips_uv_when_broken_venv_and_uses_pip(monkeypatch, tmp_path: Path) -> None:
    """When .venv has a broken python symlink, skip uv and go straight to pip."""
    from zsc import cli as cli_mod

    # Create a broken .venv/bin/python3 symlink under tmp_path
    venv_bin = tmp_path / ".venv" / "bin"
    venv_bin.mkdir(parents=True, exist_ok=True)
    broken_python = venv_bin / "python3"
    broken_python.symlink_to(tmp_path / "missing-python")

    monkeypatch.setattr(cli_mod.Path, "cwd", classmethod(lambda cls: tmp_path))

    # uv is available, but should be skipped due to broken venv
    monkeypatch.setattr(cli_mod.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None)

    calls: list[list[str]] = []

    class Result:
        def __init__(self, returncode: int) -> None:
            self.returncode = returncode

    def fake_run(cmd, check=False):  # type: ignore[no-untyped-def]
        calls.append(cmd)
        return Result(0)

    monkeypatch.setattr(cli_mod.subprocess, "run", fake_run)

    result = runner.invoke(app, ["-U"])
    assert result.exit_code == 0

    # Only pip-based install should be used
    assert ["uv", "tool", "install", "zsc", "--upgrade"] not in calls
    assert [str(Path(sys.executable)), "-m", "pip", "install", "-U", "zsc"] in [
        [str(c) for c in cmd] for cmd in calls
    ]
    assert "Detected broken virtualenv interpreter" in result.stderr


def test_upgrade_falls_back_to_pip_when_uv_fails(monkeypatch, tmp_path: Path) -> None:
    """When uv is available but fails (e.g. no virtualenv), zsc -U should fall back to pip."""
    from zsc import cli as cli_mod

    # Simulate running in a temporary directory (e.g. HOME) without a .venv
    monkeypatch.setattr(cli_mod.Path, "cwd", classmethod(lambda cls: tmp_path))

    # uv is available
    monkeypatch.setattr(
        cli_mod.shutil,
        "which",
        lambda name: "/usr/bin/uv" if name == "uv" else None,
    )

    calls: list[list[str]] = []

    class Result:
        def __init__(self, returncode: int) -> None:
            self.returncode = returncode

    def fake_run(cmd, check=False):  # type: ignore[no-untyped-def]
        calls.append(cmd)
        # First call (uv) fails with non-zero return code; second call (pip) succeeds.
        if cmd[0] == "uv":
            return Result(1)
        return Result(0)

    monkeypatch.setattr(cli_mod.subprocess, "run", fake_run)

    result = runner.invoke(app, ["-U"])
    assert result.exit_code == 0

    # uv should have been attempted first
    assert ["uv", "tool", "install", "zsc", "--upgrade"] in calls

    # Then fallback to pip should be used
    normalized_calls = [[str(c) for c in cmd] for cmd in calls]
    assert [str(Path(sys.executable)), "-m", "pip", "install", "-U", "zsc"] in normalized_calls

    # User-facing messages should reflect uv failure and pip-based completion
    assert "uv-based upgrade failed" in result.stderr
    assert "Upgrade finished via pip" in result.stdout

