import argparse
import json
from pathlib import Path


def read_json(path):
    file_path = Path(path)
    if file_path.stat().st_size > 1024 * 1024:
        raise ValueError("文件不能超过 1 MiB")
    return json.loads(file_path.read_text(encoding="utf-8"))


def check_results(rows, expected):
    if not isinstance(rows, list) or not isinstance(expected, list):
        raise ValueError("输入必须为 JSON 数组")
    if not expected:
        raise ValueError("问题清单不能为空")
    expected_map = {item["query_id"]: item for item in expected}
    if len(expected_map) != len(expected):
        raise ValueError("问题清单出现重复 query_id")
    seen = set()
    failures = []
    for row in rows:
        if not isinstance(row, dict):
            raise ValueError("每条结果必须为对象")
        query_id = row.get("query_id")
        if not isinstance(query_id, str) or query_id not in expected_map:
            failures.append("发现未知或无效 query_id")
            continue
        if query_id in seen:
            failures.append(f"{query_id}: 重复结果")
            continue
        seen.add(query_id)
        case = expected_map[query_id]
        answer = row.get("answer")
        sources = row.get("source_ids")
        if not isinstance(answer, str) or not answer.strip():
            failures.append(f"{query_id}: 缺少真实回答")
            continue
        if not isinstance(sources, list) or not all(isinstance(s, str) for s in sources):
            failures.append(f"{query_id}: source_ids 必须是字符串数组")
            continue
        if len(set(sources)) != len(sources) or set(sources) != set(case["source_ids"]):
            failures.append(f"{query_id}: 引用编号不符")
        if type(row.get("answerable")) is not bool or row["answerable"] != case["answerable"]:
            failures.append(f"{query_id}: answerable 不符")
        for phrase in case["must_include"]:
            if phrase not in answer:
                failures.append(f"{query_id}: 固定事实或拒答文本缺失")
    for query_id in sorted(set(expected_map) - seen):
        failures.append(f"{query_id}: 缺少结果")
    return failures


def main():
    parser = argparse.ArgumentParser(description="仅离线检查 RAG 固定事实与引用，无 API 请求。")
    parser.add_argument("results")
    parser.add_argument("--checklist", default=str(Path(__file__).with_name("rag-checklist.json")))
    args = parser.parse_args()
    try:
        expected = read_json(args.checklist)
        failures = check_results(read_json(args.results), expected)
    except (OSError, ValueError, KeyError, TypeError) as error:
        parser.exit(2, f"无法检查：{error}\n")
    if failures:
        for failure in failures:
            print(failure)
        raise SystemExit(1)
    print(f"{len(expected)} 项固定事实与引用检查通过；仍需人工核对原文、检索日志和回答语义。")


if __name__ == "__main__":
    main()
