"""test_load_otp_sysmodules_2553plus14.py — task-2553+14 Track B regression.

Locks in the `_load_otp()` sys.modules registration hardening (task-2553+12
post-hold-diagnosis root cause). Three guarantees:

  (1) post-hardening: the 3 F1 cases routed through `_load_otp()` PASS;
  (2) static (AST) + runtime assert: `_load_otp()` registers
      `sys.modules[spec.name] = mod` *before* `exec_module`;
  (3) smoke false-negative guard: an unregistered-loader replica of the
      pre-fix form deterministically raises the Python 3.12 `@dataclass`
      `dataclasses.py:749` AttributeError — proving a regression back to the
      pre-2553+14 form is *detected*, never silently false-negative.

Scope: test infra only. production code (anu_v2/owner_trigger_pat.py)
untouched; `_load_otp_streaming` and other PASS-path helpers unmodified.
"""

from __future__ import annotations

import ast
import importlib.util
import inspect
import sys
import textwrap
from pathlib import Path

import pytest

_WS = Path(__file__).resolve().parents[2]
if str(_WS) not in sys.path:
    sys.path.insert(0, str(_WS))

_HF_PATH = _WS / "tests/regression/test_owner_trigger_2553_plus1_high_fix.py"
_OTP_PATH = _WS / "anu_v2/owner_trigger_pat.py"


def _import_highfix():
    """Load the hardened high-fix test module (correctly registered)."""
    spec = importlib.util.spec_from_file_location(
        "hf_under_test_2553p14", _HF_PATH
    )
    assert spec is not None and spec.loader is not None
    mod = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = mod
    try:
        spec.loader.exec_module(mod)
    finally:
        sys.modules.pop(spec.name, None)
    return mod


def test_f1_three_pass_posthardening():
    """(1) Post-hardening: the 3 F1 cases via `_load_otp()` PASS."""
    hf = _import_highfix()
    otp = hf._load_otp()
    assert otp.ALLOWED_COMMENT_BODY == "/gemini review"
    with pytest.raises(Exception) as ei_ep:
        otp._assert_args_allowlist(
            ["api", "-X", "POST", "/repos/o/r/issues/1/labels",
             "-f", "body=/gemini review"],
            "o", "r", 1,
        )
    assert otp.ERR_ENDPOINT_NOT_ALLOWED in str(ei_ep.value)
    with pytest.raises(Exception) as ei_body:
        otp._assert_args_allowlist(
            ["api", "-X", "POST", "/repos/o/r/issues/1/comments",
             "-f", "body=please merge"],
            "o", "r", 1,
        )
    assert otp.ERR_BODY_NOT_ALLOWED in str(ei_body.value)


def test_load_otp_sysmodules_registration_static():
    """(2a) AST: `_load_otp` assigns `sys.modules[...] = mod` before exec_module."""
    hf = _import_highfix()
    src = textwrap.dedent(inspect.getsource(hf._load_otp))
    tree = ast.parse(src)
    fn = tree.body[0]
    assert isinstance(fn, ast.FunctionDef)

    reg_lineno = None
    exec_lineno = None
    for n in ast.walk(fn):
        if isinstance(n, ast.Assign):
            for tgt in n.targets:
                # task-2553+18 (Gemini T2): require the subscript target to be
                # exactly `sys.modules[spec.name]` — not an arbitrary
                # sys.modules[...] assignment — so a refactor that registers a
                # different key is correctly rejected (precision over breadth).
                if (
                    isinstance(tgt, ast.Subscript)
                    and isinstance(tgt.value, ast.Attribute)
                    and tgt.value.attr == "modules"
                    and isinstance(tgt.value.value, ast.Name)
                    and tgt.value.value.id == "sys"
                    and isinstance(tgt.slice, ast.Attribute)
                    and tgt.slice.attr == "name"
                    and isinstance(tgt.slice.value, ast.Name)
                    and tgt.slice.value.id == "spec"
                ):
                    reg_lineno = n.lineno
        # task-2553+18 (Gemini T2): require the call to be specifically
        # `spec.loader.exec_module(...)`, so an exec_module call on an
        # unrelated/incorrect loader is not accepted as satisfying the
        # registration-before-exec guarantee.
        if (
            isinstance(n, ast.Call)
            and isinstance(n.func, ast.Attribute)
            and n.func.attr == "exec_module"
            and isinstance(n.func.value, ast.Attribute)
            and n.func.value.attr == "loader"
            and isinstance(n.func.value.value, ast.Name)
            and n.func.value.value.id == "spec"
        ):
            exec_lineno = n.lineno

    assert reg_lineno is not None, (
        "_load_otp must register sys.modules[spec.name] = mod "
        "(regression to pre-task-2553+14 unregistered form)"
    )
    assert exec_lineno is not None, "_load_otp must call spec.loader.exec_module(mod)"
    assert reg_lineno < exec_lineno, (
        "sys.modules registration must precede exec_module "
        f"(reg@{reg_lineno} exec@{exec_lineno})"
    )


def test_load_otp_sysmodules_registration_runtime():
    """(2b) Runtime: after `_load_otp()` the module is registered & identical."""
    hf = _import_highfix()
    otp = hf._load_otp()
    # task-2553+18 (Gemini T1): derive the registration key dynamically from
    # the loaded module (== _load_otp's spec.name) instead of duplicating the
    # string literal hardcoded in test_owner_trigger_2553_plus1_high_fix.py.
    spec_name = otp.__name__
    try:
        assert spec_name in sys.modules, (
            f"_load_otp did not register sys.modules[{spec_name!r}]"
        )
        assert sys.modules[spec_name] is otp, (
            "registered module object is not the one returned by _load_otp"
        )
    finally:
        sys.modules.pop(spec_name, None)


def test_unregistered_loader_falsenegative_guard():
    """(3) Smoke guard: the pre-fix unregistered form is provably broken on
    py3.12 (raises AttributeError @ dataclasses.py:749), while the registered
    form loads cleanly. Detects any silent regression to the pre-fix loader.
    """
    if sys.version_info < (3, 12):
        pytest.skip("dataclasses.py:749 None-deref fault is Python >= 3.12")

    # Pre-fix replica: NO sys.modules registration -> deterministic AttributeError.
    spec_bad = importlib.util.spec_from_file_location(
        "otp_unreg_guard_2553p14", _OTP_PATH
    )
    assert spec_bad is not None and spec_bad.loader is not None
    mod_bad = importlib.util.module_from_spec(spec_bad)
    sys.modules.pop(spec_bad.name, None)
    try:
        with pytest.raises(AttributeError) as ei:
            spec_bad.loader.exec_module(mod_bad)
        assert "__dict__" in str(ei.value), (
            f"expected py3.12 @dataclass None-deref, got: {ei.value!r}"
        )
    finally:
        sys.modules.pop(spec_bad.name, None)

    # Corrected replica: WITH registration -> loads without error.
    spec_ok = importlib.util.spec_from_file_location(
        "otp_reg_guard_2553p14", _OTP_PATH
    )
    assert spec_ok is not None and spec_ok.loader is not None
    mod_ok = importlib.util.module_from_spec(spec_ok)
    sys.modules[spec_ok.name] = mod_ok
    try:
        spec_ok.loader.exec_module(mod_ok)  # must not raise
        assert mod_ok.ALLOWED_COMMENT_BODY == "/gemini review"
    finally:
        sys.modules.pop(spec_ok.name, None)
