# -*- coding: utf-8 -*-
"""CLI 参数和 Flow 类型文档覆盖完整性验证。

**Validates: Requirements 7.1, 7.2**

Property 6: 对于所有在 cli/main.py 的 parse_args() 中定义的 CLI 参数，
README.md 或 base_task_mechanism.md 中应包含该参数的说明。

Property 7: 对于所有在 FlowRunner.FLOW_LAYERS 中定义的 Flow 类型，
README.md 中应包含该 Flow 类型的层组合说明。
"""
# Feature: etl-task-documentation, Property 6 & 7

from __future__ import annotations

import ast
import re
from pathlib import Path

import pytest

# ── 常量 ──────────────────────────────────────────────────────

_PROJECT_ROOT = Path(__file__).resolve().parents[2]
_CLI_MAIN_PATH = _PROJECT_ROOT / "cli" / "main.py"
_README_PATH = _PROJECT_ROOT / "docs" / "etl_tasks" / "README.md"
_BASE_MECHANISM_PATH = _PROJECT_ROOT / "docs" / "etl_tasks" / "base_task_mechanism.md"


# ── 辅助函数：通过 AST 解析 parse_args() 中的 CLI 参数名 ─────

def _extract_cli_params_via_ast() -> list[str]:
    """从 cli/main.py 的 parse_args() 函数中，通过 AST 提取所有 add_argument 的参数名。

    只提取以 '--' 开头的长参数名（如 '--store-id'），忽略位置参数。
    当 add_argument 有多个名称时（如 '--api-token', '--token'），取第一个 '--' 开头的名称。
    """
    source = _CLI_MAIN_PATH.read_text(encoding="utf-8")
    tree = ast.parse(source, filename=str(_CLI_MAIN_PATH))

    params: list[str] = []

    # 找到 parse_args 函数定义
    for node in ast.walk(tree):
        if not isinstance(node, ast.FunctionDef) or node.name != "parse_args":
            continue

        # 遍历函数体中的所有 add_argument 调用
        for child in ast.walk(node):
            if not isinstance(child, ast.Call):
                continue
            # 匹配 parser.add_argument(...) 或 xxx.add_argument(...)
            func = child.func
            if not (isinstance(func, ast.Attribute) and func.attr == "add_argument"):
                continue

            # 从位置参数中提取 '--xxx' 形式的参数名
            for arg in child.args:
                if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
                    val = arg.value
                    if val.startswith("--"):
                        params.append(val)
                        break  # 取第一个 '--' 开头的名称即可

    return sorted(set(params))


# ── 辅助函数：提取 FLOW_LAYERS 的键 ──────────────────────

def _extract_pipeline_types() -> list[str]:
    """从 FlowRunner.FLOW_LAYERS 获取所有 Flow 类型名称。

    直接导入 FLOW_LAYERS 字典，避免实例化 FlowRunner。
    """
    # 通过 AST 解析 flow_runner.py 提取 FLOW_LAYERS 的键
    pr_path = _PROJECT_ROOT / "orchestration" / "flow_runner.py"
    source = pr_path.read_text(encoding="utf-8")
    tree = ast.parse(source, filename=str(pr_path))

    for node in ast.walk(tree):
        if not isinstance(node, ast.ClassDef) or node.name != "FlowRunner":
            continue
        for item in node.body:
            if not isinstance(item, (ast.Assign, ast.AnnAssign)):
                continue
            # 匹配 FLOW_LAYERS = {...} 或 FLOW_LAYERS: ... = {...}
            targets = (
                [item.target] if isinstance(item, ast.AnnAssign) else item.targets
            )
            for target in targets:
                if isinstance(target, ast.Name) and target.id == "FLOW_LAYERS":
                    value = item.value
                    if isinstance(value, ast.Dict):
                        keys: list[str] = []
                        for k in value.keys:
                            if isinstance(k, ast.Constant) and isinstance(k.value, str):
                                keys.append(k.value)
                        return sorted(keys)

    raise RuntimeError("未能从 flow_runner.py 中解析出 FLOW_LAYERS")


# ── 测试数据准备 ──────────────────────────────────────────────

_CLI_PARAMS: list[str] = _extract_cli_params_via_ast()
_PIPELINE_TYPES: list[str] = _extract_pipeline_types()


# ── Fixtures ──────────────────────────────────────────────────

@pytest.fixture(scope="module")
def readme_content() -> str:
    """读取 README.md 全文。"""
    assert _README_PATH.exists(), f"文档文件不存在: {_README_PATH}"
    return _README_PATH.read_text(encoding="utf-8")


@pytest.fixture(scope="module")
def base_mechanism_content() -> str:
    """读取 base_task_mechanism.md 全文。"""
    assert _BASE_MECHANISM_PATH.exists(), f"文档文件不存在: {_BASE_MECHANISM_PATH}"
    return _BASE_MECHANISM_PATH.read_text(encoding="utf-8")


# ── Property 6: CLI 参数文档覆盖完整性 ────────────────────────

@pytest.mark.parametrize("param", _CLI_PARAMS, ids=_CLI_PARAMS)
def test_cli_param_in_docs(param: str, readme_content: str, base_mechanism_content: str):
    """Property 6: 每个 CLI 参数在 README.md 或 base_task_mechanism.md 中有对应说明。

    **Validates: Requirements 7.1**
    """
    # 参数名以反引号包裹或直接出现均可
    combined = readme_content + "\n" + base_mechanism_content
    assert param in combined, (
        f"CLI 参数 '{param}' 在 parse_args() 中定义，"
        f"但未在 README.md 或 base_task_mechanism.md 中找到对应说明"
    )


# ── Property 7: Flow 类型文档覆盖完整性 ────────────────────────

@pytest.mark.parametrize("pipeline_type", _PIPELINE_TYPES, ids=_PIPELINE_TYPES)
def test_pipeline_type_in_readme(pipeline_type: str, readme_content: str):
    """Property 7: 每个 Flow 类型在 README.md 中有对应的层组合说明。

    **Validates: Requirements 7.2**
    """
    assert pipeline_type in readme_content, (
        f"Flow 类型 '{pipeline_type}' 在 FLOW_LAYERS 中定义，"
        f"但未在 README.md 中找到对应的层组合说明"
    )
