"""CLI 命令测试 — Issue #246 Step 5

覆盖 15 个 CLI 命令的 happy path + 错误处理。
使用 Click CliRunner 隔离测试，不影响真实数据。
"""

from __future__ import annotations

import json
from pathlib import Path
from unittest.mock import patch

import pytest
from click.testing import CliRunner

from index1.cli import main


@pytest.fixture()
def runner():
    return CliRunner()


@pytest.fixture()
def tmp_config(tmp_path):
    """写入临时配置文件，避免影响全局"""
    config_file = tmp_path / "config.json"
    config_file.write_text(json.dumps({
        "db_path": str(tmp_path / "test.db"),
        "cognitive_db_path": str(tmp_path / "test_cog.db"),
        "embed_backend": "onnx",
        "watch_paths": [str(tmp_path)],
    }))
    return config_file


# ═══════════════════════════════════════════
# P0: status, search, index
# ═══════════════════════════════════════════


class TestStatusCommand:
    """P0: index1 status"""

    def test_status_basic(self, runner):
        result = runner.invoke(main, ["status"])
        # 可能报错（无 DB）但不能崩溃
        assert result.exit_code in (0, 1)

    def test_status_no_crash(self, runner):
        """即使没有数据库也不能 crash"""
        with patch.dict("os.environ", {"INDEX1_HOME": "/tmp/nonexistent_index1_test"}):
            result = runner.invoke(main, ["status"])
            assert result.exit_code in (0, 1)


class TestSearchCommand:
    """P0: index1 search"""

    def test_search_no_args(self, runner):
        """无参数 → 应显示帮助或报错"""
        result = runner.invoke(main, ["search"])
        assert result.exit_code != 0  # 缺少 query 参数

    def test_search_basic(self, runner):
        """正常搜索 — 可能无结果但不崩溃"""
        result = runner.invoke(main, ["search", "test"])
        assert result.exit_code in (0, 1)


class TestIndexCommand:
    """P0: index1 index"""

    def test_index_no_path(self, runner):
        """无路径参数 → 使用默认 watch_paths"""
        result = runner.invoke(main, ["index"])
        assert result.exit_code in (0, 1)

    def test_index_nonexistent_path(self, runner):
        """路径不存在 → 报错"""
        result = runner.invoke(main, ["index", "/nonexistent/path/xyz123"])
        assert result.exit_code != 0


# ═══════════════════════════════════════════
# P1: config
# ═══════════════════════════════════════════


class TestConfigCommand:
    """P1: index1 config"""

    def test_config_list(self, runner):
        """查看全部配置"""
        result = runner.invoke(main, ["config"])
        assert result.exit_code == 0
        assert len(result.output) > 0

    def test_config_get_key(self, runner):
        """查看单个配置项"""
        result = runner.invoke(main, ["config", "embed_backend"])
        assert result.exit_code == 0

    def test_config_invalid_key(self, runner):
        """无效 key → 报错或显示错误信息"""
        result = runner.invoke(main, ["config", "nonexistent_key_xyz"])
        # 应该报错但不崩溃
        assert result.exit_code in (0, 1)


# ═══════════════════════════════════════════
# P1: doctor, cog doctor, cog status, cog recall, cog list
# ═══════════════════════════════════════════


class TestDoctorCommand:
    """P1: index1 doctor"""

    def test_doctor_basic(self, runner):
        result = runner.invoke(main, ["doctor"])
        assert result.exit_code in (0, 1)


class TestCogCommands:
    """P1: index1 cog 子命令"""

    def test_cog_doctor(self, runner):
        result = runner.invoke(main, ["cog", "doctor"])
        assert result.exit_code in (0, 1)

    def test_cog_status(self, runner):
        result = runner.invoke(main, ["cog", "cog-status"])
        assert result.exit_code in (0, 1)

    def test_cog_recall(self, runner):
        """cog recall 需要 query 参数"""
        result = runner.invoke(main, ["cog", "recall", "test query"])
        assert result.exit_code in (0, 1)

    def test_cog_recall_no_args(self, runner):
        result = runner.invoke(main, ["cog", "recall"])
        assert result.exit_code != 0  # 缺少 query 参数

    def test_cog_list(self, runner):
        result = runner.invoke(main, ["cog", "list"])
        assert result.exit_code in (0, 1)

    def test_cog_rebuild_dry_run(self, runner):
        """cog rebuild --dry-run 不实际修改"""
        result = runner.invoke(main, ["cog", "rebuild", "--dry-run"])
        assert result.exit_code in (0, 1)


# ═══════════════════════════════════════════
# P1: setup
# ═══════════════════════════════════════════


class TestSetupCommand:
    """P1: index1 setup"""

    def test_setup_status(self, runner):
        """setup --status 查看插件状态"""
        result = runner.invoke(main, ["setup", "--status"])
        assert result.exit_code in (0, 1)
        # 应该有输出描述插件状态
        assert len(result.output) > 0


# ═══════════════════════════════════════════
# P2: repo-map, symbols, web, backfill
# ═══════════════════════════════════════════


class TestRepoMapCommand:
    """P2: index1 repo-map"""

    def test_repo_map(self, runner):
        result = runner.invoke(main, ["repo-map"])
        assert result.exit_code in (0, 1)


class TestSymbolsCommand:
    """P2: index1 symbols"""

    def test_symbols_no_file(self, runner):
        """无参数 → 显示统计"""
        result = runner.invoke(main, ["symbols"])
        assert result.exit_code in (0, 1)

    def test_symbols_nonexistent_file(self, runner):
        """文件不存在"""
        result = runner.invoke(main, ["symbols", "/nonexistent_xyz.py"])
        assert result.exit_code in (0, 1, 2)


class TestBackfillCommand:
    """P2: index1 backfill"""

    def test_backfill(self, runner):
        result = runner.invoke(main, ["backfill"])
        assert result.exit_code in (0, 1)


class TestWebCommand:
    """P2: index1 web — 只验证帮助信息，不启动服务"""

    def test_web_help(self, runner):
        result = runner.invoke(main, ["web", "--help"])
        assert result.exit_code == 0
        assert "Web UI" in result.output or "监听端口" in result.output


class TestHookCommand:
    """P1: index1 hook"""

    def test_hook_help(self, runner):
        result = runner.invoke(main, ["hook", "--help"])
        assert result.exit_code == 0

    def test_hook_no_event(self, runner):
        """无事件名 → 从环境变量检测（没有时报错）"""
        result = runner.invoke(main, ["hook"])
        assert result.exit_code in (0, 1)


# ═══════════════════════════════════════════
# 版本和帮助
# ═══════════════════════════════════════════


class TestHelpAndVersion:
    def test_help(self, runner):
        result = runner.invoke(main, ["--help"])
        assert result.exit_code == 0
        assert "index1" in result.output

    def test_version(self, runner):
        result = runner.invoke(main, ["--version"])
        assert result.exit_code == 0
