#!/usr/bin/env python3
"""窗译源码的一站式导航、影响分析、检查和快照入口。"""

from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
from typing import Any, Iterable


REPO_ROOT = Path(__file__).resolve().parent.parent
MAP_PATH = REPO_ROOT / "Developer" / "source-map.json"
KNOWN_TEST_SUITES = {
    "RealtimeCoreTests",
    "TranslationProviderTests",
    "TutorialContentTests",
    "LicenseCoreTests",
    "AppLoggerTests",
    "OCRCoreTests",
    "TranslationCoreTests",
    "CacheCoreTests",
    "LocalModelDownloadPolicyTests",
}


def load_map() -> dict[str, Any]:
    with MAP_PATH.open("r", encoding="utf-8") as handle:
        return json.load(handle)


def searchable_text(item: dict[str, Any]) -> str:
    values: list[str] = []
    for key, value in item.items():
        if isinstance(value, str):
            values.append(value)
        elif isinstance(value, list):
            values.extend(str(entry) for entry in value)
    return " ".join(values).lower()


def matching_feature(data: dict[str, Any], selector: str) -> dict[str, Any] | None:
    normalized = selector.lower()
    exact = [
        feature
        for feature in data["features"]
        if normalized in {feature["id"].lower(), feature["name"].lower()}
    ]
    if exact:
        return exact[0]
    partial = [
        feature for feature in data["features"] if normalized in searchable_text(feature)
    ]
    return partial[0] if len(partial) == 1 else None


def print_paths(paths: Iterable[str], indent: str = "  - ") -> None:
    for path in paths:
        print(f"{indent}{path}")


def print_feature(feature: dict[str, Any]) -> None:
    print(f"[{feature['id']}] {feature['name']}")
    print(f"用途：{feature['description']}")
    print("直接查看：")
    print_paths(feature["paths"])
    if feature.get("protectedRules"):
        print("不可破坏的规则：")
        print_paths(feature["protectedRules"])
    suites = feature.get("testSuites", [])
    print("对应检查：" + ("、".join(suites) if suites else "类型检查与源码索引检查"))
    print(f"影响分析：./scripts/dev impact <文件路径>")
    print(f"功能快照：./scripts/dev snapshot {feature['id']} <功能版本>")


def command_list(data: dict[str, Any], _args: argparse.Namespace) -> int:
    print(f"窗译 {data['appVersion']}（Build {data['build']}）源码功能目录")
    for feature in data["features"]:
        print(f"  {feature['id']:<24} {feature['name']}")
    return 0


def command_feature(data: dict[str, Any], args: argparse.Namespace) -> int:
    feature = matching_feature(data, args.selector)
    if feature is None:
        print(f"没有唯一匹配的功能：{args.selector}", file=sys.stderr)
        print("先运行 ./scripts/dev find <关键词> 查看候选项。", file=sys.stderr)
        return 2
    print_feature(feature)
    return 0


def command_find(data: dict[str, Any], args: argparse.Namespace) -> int:
    query = " ".join(args.query).strip().lower()
    if not query:
        print("请输入要找的功能或问题。", file=sys.stderr)
        return 2
    found = False
    for feature in data["features"]:
        if query in searchable_text(feature):
            if not found:
                print("功能定位")
            found = True
            print(f"  [{feature['id']}] {feature['name']}")
            print_paths(feature["paths"], indent="    - ")
    for route in data["bugRoutes"]:
        if query in searchable_text(route):
            print(f"故障路线：{route['symptom']}")
            print("  先查看：")
            print_paths(route["firstLook"], indent="    - ")
            print("  相关功能：" + "、".join(route["featureIds"]))
            found = True
    for module in data["modules"]:
        if query in searchable_text(module):
            print(f"模块目录：[{module['id']}] {module['name']} -> {module['path']}")
            found = True
    if not found:
        print(f"索引中暂时没有“{query}”。可运行 ./scripts/dev list 查看全部功能。")
        return 1
    return 0


def path_matches(mapped: str, requested: str) -> bool:
    mapped_path = Path(mapped)
    requested_path = Path(requested)
    return requested_path == mapped_path or mapped_path in requested_path.parents


def command_impact(data: dict[str, Any], args: argparse.Namespace) -> int:
    requested = Path(args.path)
    if requested.is_absolute():
        try:
            requested = requested.resolve().relative_to(REPO_ROOT)
        except ValueError:
            print("文件不在窗译源码目录内。", file=sys.stderr)
            return 2
    requested_text = requested.as_posix().lstrip("./")
    matches = [
        feature
        for feature in data["features"]
        if any(path_matches(path, requested_text) for path in feature["paths"])
    ]
    if not matches:
        print(f"没有为 {requested_text} 登记功能影响；请补充 Developer/source-map.json。")
        return 1
    print(f"{requested_text} 影响以下功能：")
    for feature in matches:
        print(f"  [{feature['id']}] {feature['name']}")
        for rule in feature.get("protectedRules", []):
            print(f"    保护规则：{rule}")
    suites = sorted({suite for feature in matches for suite in feature.get("testSuites", [])})
    if suites:
        print("修改后应检查：" + "、".join(suites))
    return 0


def validate_map(data: dict[str, Any], verbose: bool = True) -> list[str]:
    errors: list[str] = []
    if data.get("schemaVersion") != 1:
        errors.append("source-map.json schemaVersion 必须为 1")
    feature_ids = [feature["id"] for feature in data.get("features", [])]
    if len(feature_ids) != len(set(feature_ids)):
        errors.append("功能 id 存在重复")
    module_ids = [module["id"] for module in data.get("modules", [])]
    if len(module_ids) != len(set(module_ids)):
        errors.append("模块 id 存在重复")
    for path in data.get("entrypoints", []):
        if not (REPO_ROOT / path).exists():
            errors.append(f"启动入口不存在：{path}")
    for module in data.get("modules", []):
        if not (REPO_ROOT / module["path"]).is_dir():
            errors.append(f"模块目录不存在：{module['path']}")
    for feature in data.get("features", []):
        for path in feature.get("paths", []):
            candidate = Path(path)
            if candidate.is_absolute() or ".." in candidate.parts:
                errors.append(f"功能 {feature['id']} 包含不安全路径：{path}")
            elif not (REPO_ROOT / candidate).exists():
                errors.append(f"功能 {feature['id']} 的路径不存在：{path}")
        for suite in feature.get("testSuites", []):
            if suite not in KNOWN_TEST_SUITES:
                errors.append(f"功能 {feature['id']} 使用未知检查：{suite}")
    known_ids = set(feature_ids)
    for route in data.get("bugRoutes", []):
        for feature_id in route.get("featureIds", []):
            if feature_id not in known_ids:
                errors.append(f"故障路线引用未知功能：{feature_id}")
        for path in route.get("firstLook", []):
            if not (REPO_ROOT / path).exists():
                errors.append(f"故障路线文件不存在：{path}")
    source_root = REPO_ROOT / "mado-improved" / "Sources"
    for swift_file in source_root.rglob("*.swift"):
        relative = swift_file.relative_to(REPO_ROOT).as_posix()
        if not any(path_matches(module["path"], relative) for module in data["modules"]):
            errors.append(f"Swift 文件未归入模块：{relative}")
    if verbose:
        if errors:
            for error in errors:
                print("错误：" + error, file=sys.stderr)
        else:
            print(
                f"源码索引有效：{len(data['modules'])} 个模块、"
                f"{len(data['features'])} 项功能、{len(data['bugRoutes'])} 条故障路线。"
            )
    return errors


def command_validate(data: dict[str, Any], _args: argparse.Namespace) -> int:
    return 1 if validate_map(data) else 0


def run_checked(command: list[str], env: dict[str, str] | None = None) -> None:
    print("运行：" + " ".join(command), flush=True)
    subprocess.run(command, cwd=REPO_ROOT, env=env, check=True)


def command_check(data: dict[str, Any], args: argparse.Namespace) -> int:
    if validate_map(data):
        return 1
    if args.selector == "all":
        run_checked(["./scripts/typecheck.sh"])
        run_checked(["./scripts/run_tests.sh"])
        return 0
    feature = matching_feature(data, args.selector)
    if feature is None:
        print(f"没有唯一匹配的功能：{args.selector}", file=sys.stderr)
        return 2
    run_checked(["./scripts/typecheck.sh"])
    suites = feature.get("testSuites", [])
    for suite in suites:
        suite_env = os.environ.copy()
        suite_env["MADO_TEST_FILTER"] = suite
        run_checked(["./scripts/run_tests.sh"], env=suite_env)
    if not suites:
        run_checked(["python3", "scripts/audit_source_structure.py"])
    print(f"{feature['name']} 的登记检查已通过。")
    return 0


def command_snapshot(data: dict[str, Any], args: argparse.Namespace) -> int:
    feature = matching_feature(data, args.selector)
    if feature is None:
        print(f"没有唯一匹配的功能：{args.selector}", file=sys.stderr)
        return 2
    command = [
        sys.executable,
        "scripts/archive_feature_snapshot.py",
        feature["id"],
        args.version,
    ]
    run_checked(command)
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="窗译源码导航和维护入口")
    commands = parser.add_subparsers(dest="command", required=True)
    commands.add_parser("list", help="列出全部功能")
    find_parser = commands.add_parser("find", help="按功能名或故障现象找源码")
    find_parser.add_argument("query", nargs="+")
    feature_parser = commands.add_parser("feature", help="查看一项功能的完整源码入口")
    feature_parser.add_argument("selector")
    impact_parser = commands.add_parser("impact", help="查看修改某文件会影响哪些功能")
    impact_parser.add_argument("path")
    commands.add_parser("validate", help="校验索引中的文件和关系")
    check_parser = commands.add_parser("check", help="执行一项功能或全部源码检查")
    check_parser.add_argument("selector")
    snapshot_parser = commands.add_parser("snapshot", help="保存不可覆盖的单项功能源码快照")
    snapshot_parser.add_argument("selector")
    snapshot_parser.add_argument("version")
    return parser


def main() -> int:
    data = load_map()
    args = build_parser().parse_args()
    commands = {
        "list": command_list,
        "find": command_find,
        "feature": command_feature,
        "impact": command_impact,
        "validate": command_validate,
        "check": command_check,
        "snapshot": command_snapshot,
    }
    return commands[args.command](data, args)


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except subprocess.CalledProcessError as error:
        print(f"操作失败，退出码：{error.returncode}", file=sys.stderr)
        raise SystemExit(error.returncode)
