"""
Python 3.12+ 语法/API 兼容性检测
检测 aacode 代码中是否误用了 Python 3.12 才有的语法或 API，
因为项目要求 supports >=3.10（或 >=3.8）。
"""
import ast
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

import pytest

ROOT = Path(__file__).parent.parent / "aacode"


def _all_py_files():
    for f in sorted(ROOT.rglob("*.py")):
        if ".venv" in str(f):
            continue
        yield f


def _source_lines(filepath):
    return filepath.read_text(encoding="utf-8").split("\n")


# ─── PEP 695: type 语句 ────────────────────────────────────────────

def test_no_pep695_type_statement():
    """
    PEP 695 type 语句 (Python 3.12+):
      type Point = tuple[float, float]
    在 3.11 及以下会 SyntaxError。
    """
    errors = []
    for f in _all_py_files():
        for i, line in enumerate(_source_lines(f), 1):
            # 匹配 type 关键字后跟标识符再跟 =, 且不是 type() 调用
            m = re.match(r"^\s*type\s+(\w+)\s*=", line)
            if m:
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
    assert not errors, (
        "检测到 PEP 695 type 语句 (Python 3.12+)，<3.12 会 SyntaxError:\n"
        + "\n".join(errors[:10])
    )


# ─── PEP 695: 类型参数语法 ─────────────────────────────────────────

def test_no_pep695_type_param_syntax():
    """
    PEP 695 泛型语法 (Python 3.12+):
      def func[T](x: T) -> T: ...
      class MyClass[T]: ...
    在 3.11 及以下会 SyntaxError。
    """
    errors = []
    for f in _all_py_files():
        for i, line in enumerate(_source_lines(f), 1):
            if re.search(r"\b(def|class)\s+\w+\s*\[", line):
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
    assert not errors, (
        "检测到 PEP 695 类型参数语法 (Python 3.12+)，<3.12 会 SyntaxError:\n"
        + "\n".join(errors[:10])
    )


# ─── PEP 698: @override 装饰器 ─────────────────────────────────────

def test_no_pep698_override():
    """
    PEP 698 @override 装饰器 (Python 3.12+  typing.override)，
    在 3.11 及以下 typing 模块没有 override。
    """
    errors = []
    for f in _all_py_files():
        text = f.read_text(encoding="utf-8")
        for i, line in enumerate(text.split("\n"), 1):
            if re.search(r"from\s+typing\s+import\s+.*\boverride\b", line):
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
                continue
            # 也检测 typing.override 的使用
            if re.match(r"^\s*@override\s*$", line) and "from typing import" not in line:
                pass
    assert not errors, (
        "检测到 typing.override (Python 3.12+)，<3.12 中不存在:\n"
        + "\n".join(errors[:10])
    )


# ─── PEP 701: f-string 内同引号嵌套 ────────────────────────────────

def _check_312_fstring(line):
    """
    逐字符检查一行中是否有 Python 3.12+ 才支持的 f-string 语法:
    f-string 的 {...} 表达式中出现与 f-string 界定符同种的引号。

    例如 f"hello {"world"}"  — 双引号 f-string 内 { 后有双引号
         f'{f"{x}"}'         — 单引号 f-string 内 { 后有单引号
         f"data = {json.dumps(obj)}"  — 内嵌 json.dumps 返回的 " 也会出问题

    返回 True 表示检测到问题。
    """
    i = 0
    n = len(line)
    while i < n:
        if i + 1 < n and line[i] in ("f", "F") and line[i + 1] in ('"', "'"):
            quote = line[i + 1]
            # f""" / f''' 三引号 f-string 可安全在内部嵌套单引/双引
            if i + 2 < n and line[i + 2] == quote:
                i += 3
                continue
            j = i + 2
            brace_depth = 0
            in_inner_str = None  # 跟踪 {...} 内部的字符串界定符
            while j < n:
                c = line[j]
                # 转义跳过
                if c == "\\" and j + 1 < n:
                    j += 2
                    continue
                # 处理 {...} 内的引号
                if c in ('"', "'") and brace_depth > 0:
                    if c == quote:
                        # 与 f-string 界定符同种引号出现在 {...} 内
                        return True
                    if in_inner_str is None:
                        # 不同引号 → 作为内部字符串
                        if j + 2 < n and line[j + 1] == c and line[j + 2] == c:
                            in_inner_str = c * 3
                            j += 3
                            continue
                        else:
                            in_inner_str = c
                            j += 1
                            continue
                    elif c == in_inner_str[0]:
                        if len(in_inner_str) == 3:
                            if j + 2 < n and line[j + 1] == c and line[j + 2] == c:
                                in_inner_str = None
                                j += 3
                                continue
                        else:
                            in_inner_str = None
                            j += 1
                            continue
                if in_inner_str is not None:
                    j += 1
                    continue
                # 大括号
                if c == "{":
                    brace_depth += 1
                elif c == "}":
                    brace_depth -= 1
                    if brace_depth < 0:
                        break
                elif c == "\n" or (c == quote and brace_depth == 0):
                    break
                j += 1
        i += 1
    return False


def test_no_fstring_nested_same_quotes():
    """
    PEP 701 f-string 改进 (Python 3.12+) 允许 f-string 内 {...} 表达式中
    出现与 f-string 界定符同种的引号，在 3.11 及以下会 SyntaxError。
    逐字符解析检测此类模式。
    """
    errors = []
    for f in _all_py_files():
        for i, line in enumerate(_source_lines(f), 1):
            if _check_312_fstring(line):
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
    assert not errors, (
        "检测到 f-string 表达式内同种引号 (Python 3.12+)，<3.12 会 SyntaxError:\n"
        + "\n".join(errors[:10])
    )


# ─── 3.12+ 特有 stdlib API ─────────────────────────────────────────

_312_ONLY_APIS = [
    # (文件内容模式, 版本, 说明)
    (r"\bitertools\.batched\b", "3.12", "itertools.batched"),
    (r"\bPath\s*\.\s*walk\b", "3.12", "pathlib.Path.walk()"),
    (r"\bpathlib\s*\.\s*Path\s*\.\s*walk\b", "3.12", "pathlib.Path.walk()"),
    (r"\bsys\.monitoring\b", "3.12", "sys.monitoring"),
    (r"\bos\.path\.splitroot\b", "3.12", "os.path.splitroot()"),
    (r"\bos\.path\.isjunction\b", "3.12", "os.path.isjunction()"),
    (r"\bcalendar\.(Month|Day)\b", "3.12", "calendar.Month/Day"),
    (r"from\s+warnings\s+import\s+deprecated", "3.13", "warnings.deprecated"),
    (r"warnings\.deprecated\b", "3.13", "warnings.deprecated"),
    (r"typing\.TypeAliasType\b", "3.12", "typing.TypeAliasType"),
    (r"enum\.member\b", "3.12", "enum.member"),
    (r"enum\.nonmember\b", "3.12", "enum.nonmember"),
    (r"logging\.getLevelNamesMapping\b", "3.12", "logging.getLevelNamesMapping"),
]


def test_no_312_stdlib_apis():
    """
    检查是否导入了 Python 3.12+ 才有的标准库 API。
    """
    errors = []
    for f in _all_py_files():
        text = f.read_text(encoding="utf-8")
        for pattern, ver, desc in _312_ONLY_APIS:
            for match in re.finditer(pattern, text):
                line_no = text[: match.start()].count("\n") + 1
                line = text.split("\n")[line_no - 1].strip()[:80]
                errors.append(f"{f.name}:{line_no}  {desc} (Python {ver}+)  {line}")
    assert not errors, (
        "检测到 Python 3.12+ 特有 API:\n" + "\n".join(errors[:15])
    )


# ─── typing.Self (3.11+，但如果支持 3.10 则要注意) ───────────────────

def test_no_self_from_typing():
    """
    typing.Self 在 Python 3.11 加入，如果目标 >=3.10 则需要避免直接使用
    typing.Self，改用 typing_extensions.Self。
    但如果 pyproject.toml requires-python >=3.11 则无此限制。
    这里仅做提示级检查，不做硬阻断。
    """
    pyproject = Path(__file__).parent.parent / "pyproject.toml"
    min_ver = (3, 10)
    try:
        import tomllib
    except ImportError:
        tomllib = None
    if tomllib:
        data = tomllib.load(open(pyproject, "rb"))
        req = data["project"]["requires-python"]
        m = re.match(r">=\s*(\d)\.(\d+)", req)
        if m:
            min_ver = (int(m.group(1)), int(m.group(2)))

    if min_ver >= (3, 11):
        return  # typing.Self 在 3.11 可用

    errors = []
    for f in _all_py_files():
        for i, line in enumerate(_source_lines(f), 1):
            if re.search(r"from\s+typing\s+import\s+.*\bSelf\b", line) and "typing_extensions" not in line:
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
    assert not errors, (
        "检测到 typing.Self (Python 3.11+)，但 pyproject.toml requires-python >=3.10，"
        "请使用 typing_extensions.Self:\n" + "\n".join(errors[:10])
    )


# ─── tomllib (3.11+) ───────────────────────────────────────────────

def test_no_tomllib_without_guard():
    """
    tomllib 在 Python 3.11 加入。如果代码直接 import tomllib 而没有
    try/except 回退，在 3.10 会 ImportError。
    """
    pyproject = Path(__file__).parent.parent / "pyproject.toml"
    min_ver = (3, 10)
    try:
        import tomllib
    except ImportError:
        tomllib_mod = None
    else:
        tomllib_mod = tomllib
    if tomllib_mod:
        data = tomllib_mod.load(open(pyproject, "rb"))
        req = data["project"]["requires-python"]
        m = re.match(r">=\s*(\d)\.(\d+)", req)
        if m:
            min_ver = (int(m.group(1)), int(m.group(2)))

    if min_ver >= (3, 11):
        return

    errors = []
    for f in _all_py_files():
        text = f.read_text(encoding="utf-8")
        lines = text.split("\n")
        imports_tomllib = any(
            re.match(r"^\s*import\s+tomllib\b", l) or re.match(r"^\s*from\s+tomllib\s+import", l)
            for l in lines
        )
        if imports_tomllib:
            has_guard = any("tomllib" in l and ("ImportError" in l or "except" in l) for l in lines)
            if not has_guard and "tomllib = None" not in text:
                for i, l in enumerate(lines, 1):
                    if re.match(r"^\s*import\s+tomllib\b", l) or re.match(r"^\s*from\s+tomllib\s+import", l):
                        errors.append(f"{f.name}:{i}  {l.strip()[:80]}")
                        break
            has_fallback = "tomli" in text.lower()
            if not has_guard and not has_fallback and imports_tomllib:
                pass
    assert not errors, (
        "检测到未防护的 import tomllib (Python 3.11+)，在 <3.11 会 ImportError。"
        "请添加 try/except 回退到 tomli:\n" + "\n".join(errors[:10])
    )


# ─── except* 语法 (3.11+) ──────────────────────────────────────────

def test_no_except_star():
    """
    except* 是 Python 3.11 引入的 ExceptionGroup 语法。
    """
    errors = []
    for f in _all_py_files():
        for i, line in enumerate(_source_lines(f), 1):
            if re.match(r"^\s*except\s*\*", line) or re.match(r"^\s*except\*\s", line):
                errors.append(f"{f.name}:{i}  {line.strip()[:80]}")
    assert not errors, (
        "检测到 except* 语法 (Python 3.11+):\n" + "\n".join(errors[:10])
    )
