"""Tests that src/models.py has NO top-level pydantic import.

Per the Main Thread Purity Invariant (spec.md:2.1), pydantic is heavy and
must not appear in the main-thread import chain. The two pydantic classes
(GenerateRequest, ConfirmRequest) are exposed via a module-level
__getattr__ proxy (PEP 562) that materializes them on first access.

These tests run in a fresh subprocess to ensure no warmup state leaks
from the test runner. We assert:
  - pydantic is NOT imported as a side effect of `import src.models`
  - GenerateRequest and ConfirmRequest can be imported and instantiated
  - Accessing the proxy triggers the pydantic import
  - The static audit no longer flags src/models.py for pydantic
"""

import subprocess
import sys
import textwrap
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent


def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess:
 script = textwrap.dedent(snippet)
 return subprocess.run(
  [sys.executable, "-c", script],
  capture_output=True,
  text=True,
  cwd=str(ROOT),
  timeout=30,
 )


def test_models_does_not_import_pydantic_at_module_level() -> None:
 res = _run_in_subprocess("""
  import sys
  import src.models
  print('pydantic' in sys.modules)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert res.stdout.strip() == "False", (
  f"src.models triggered pydantic import: {res.stdout}"
 )


def test_generate_request_works_when_explicitly_imported() -> None:
 res = _run_in_subprocess("""
  from src.models import GenerateRequest
  req = GenerateRequest(prompt="hello")
  print(req.prompt)
  print(req.auto_add_history)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert res.stdout.strip() == "hello\nTrue"


def test_confirm_request_works_when_explicitly_imported() -> None:
 res = _run_in_subprocess("""
  from src.models import ConfirmRequest
  req = ConfirmRequest(approved=True, script="echo hi")
  print(req.approved)
  print(req.script)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert res.stdout.strip() == "True\necho hi"


def test_pydantic_only_loaded_after_explicit_class_access() -> None:
 res = _run_in_subprocess("""
 import sys
 import src.models
 assert 'pydantic' not in sys.modules, 'pydantic leaked into sys.modules at import time'
 from src.models import GenerateRequest
 print('pydantic' in sys.modules)
 print(GenerateRequest.__bases__[0].__name__)
 print(GenerateRequest.__bases__[0].__module__)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 lines = res.stdout.strip().splitlines()
 assert lines[0] == "True", f"GenerateRequest access did not trigger pydantic import: {res.stdout}"
 assert lines[1] == "BaseModel", f"GenerateRequest base is not BaseModel: {res.stdout}"
 assert lines[2].startswith("pydantic"), f"GenerateRequest base is not from pydantic package: {res.stdout}"


def test_proxy_caches_real_class_for_repeated_access() -> None:
 res = _run_in_subprocess("""
  from src.models import GenerateRequest
  cls1 = GenerateRequest
  cls2 = GenerateRequest
  print(cls1 is cls2)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert res.stdout.strip() == "True", f"proxy did not cache the real class: {res.stdout}"


def test_generate_request_validation_rejects_missing_prompt() -> None:
 res = _run_in_subprocess("""
  from src.models import GenerateRequest
  try:
   GenerateRequest()
   print("NO_RAISE")
  except Exception as e:
   print(type(e).__name__)
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert res.stdout.strip() != "NO_RAISE", "pydantic validation did not fire on missing required field"
 assert "ValidationError" in res.stdout, f"expected ValidationError, got: {res.stdout}"


def test_audit_sees_no_pydantic_violation_in_models() -> None:
 res = _run_in_subprocess("""
  import ast
  from pathlib import Path
  root = Path('.').resolve()
  models_path = root / 'src' / 'models.py'
  tree = ast.parse(models_path.read_text(encoding='utf-8'))
  for node in tree.body:
   if isinstance(node, ast.Import):
    for alias in node.names:
     if alias.name == 'pydantic' or alias.name.startswith('pydantic.'):
      print('VIOLATION:', alias.name)
   elif isinstance(node, ast.ImportFrom):
    if node.module and (node.module == 'pydantic' or node.module.startswith('pydantic.')):
     print('VIOLATION:', node.module)
  print('OK')
 """)
 assert res.returncode == 0, f"stderr: {res.stderr}"
 assert "OK" in res.stdout, f"audit script errored: {res.stderr}"
 assert "VIOLATION" not in res.stdout, f"src/models.py still has top-level pydantic: {res.stdout}"
