# Phase 7 Task 7.8 - Regression-guard tests for audit heuristic.
# Per Phase 7 spec 22.5.5 (FR5):
# - BOUNDARY_FASTAPI classification requires ast.Raise(exc=HTTPException)
#   OR a return of Result(...) in the except body.
# - Otherwise re-classify as INTERNAL_SILENT_SWALLOW (logging body) or
#   INTERNAL_COMPLIANT (try/finally cleanup).
import ast
import sys
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))

from audit_exception_handling import (  # noqa: E402
    ExceptionVisitor,
    audit_file,
)


def _make_visitor(source: str, func_name: str):
    """Create an ExceptionVisitor positioned inside the named function."""
    tree = ast.parse(source)
    visitor = ExceptionVisitor(str(ROOT / "src" / "_test_dummy.py"))
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef) and node.name == func_name:
            visitor._func_stack = [node]
            return visitor
    raise ValueError(f"Function {func_name} not found in source")


def _find_handler(visitor):
    """Find the first Try node in the function body."""
    for node in visitor._func_stack[0].body:
        if isinstance(node, ast.Try):
            return node
    raise AssertionError("expected a try/except in function")


def test_is_api_handler_requires_http_exception_in_body():
    # OLD STYLE: only stderr.write (should NOT be BOUNDARY_FASTAPI after Phase 7)
    src = (
        "def _api_generate(controller):\n"
        " HTTPException = None\n"
        " try:\n"
        "  do_something()\n"
        " except Exception as e:\n"
        "  sys.stderr.write('err: ' + str(e))\n"
        "  sys.stderr.flush()\n"
    )
    visitor = _make_visitor(src, "_api_generate")
    handler = _find_handler(visitor).handlers[0]
    category, _ = visitor._classify_except(handler, _find_handler(visitor))
    assert category != "BOUNDARY_FASTAPI", (
        f"Phase 7 FR5 tightening failed: stale body (only stderr.write) "
        f"should NOT be BOUNDARY_FASTAPI; got {category}."
    )


def test_api_handler_with_http_exception_raise_is_boundary_fastapi():
    # NEW STYLE: raises HTTPException (the canonical FastAPI pattern)
    src = (
        "def _api_generate(controller):\n"
        " HTTPException = None\n"
        " try:\n"
        "  do_something()\n"
        " except Exception as e:\n"
        "  raise HTTPException(status_code=500, detail=str(e))\n"
    )
    visitor = _make_visitor(src, "_api_generate")
    try_node = _find_handler(visitor)
    handler = try_node.handlers[0]
    category, _ = visitor._classify_except(handler, try_node)
    assert category == "BOUNDARY_FASTAPI", (
        f"Phase 7 FR5 regression: handler with HTTPException raise should be "
        f"BOUNDARY_FASTAPI; got {category}."
    )


def test_non_api_handler_with_logging_is_still_internal_compliant():
    # Non-_api_* function with logging-only except body
    src = (
        "def regular_handler():\n"
        " try:\n"
        "  do_something()\n"
        " except Exception as e:\n"
        "  logging.getLogger('x').debug('err: %s', e)\n"
        "  print('err: ' + str(e))\n"
    )
    visitor = _make_visitor(src, "regular_handler")
    try_node = _find_handler(visitor)
    handler = try_node.handlers[0]
    category, _ = visitor._classify_except(handler, try_node)
    assert category in ("INTERNAL_COMPLIANT", "INTERNAL_SILENT_SWALLOW", "INTERNAL_BROAD_CATCH"), (
        f"Non-api handler should NOT be BOUNDARY_FASTAPI; got {category}."
    )


def test_15_existing_fastapi_sites_remain_classified():
    # The 13 BOUNDARY_FASTAPI sites in src/app_controller.py must remain
    # classified after the heuristic tightening (Phase 7 FR5).
    # Note: src/api_hooks.py functions do NOT have _api_ prefix, so they
    # were never classified BOUNDARY_FASTAPI; the 13 sites are all in
    # _api_* handlers in app_controller.py.
    app_controller_path = ROOT / "src" / "app_controller.py"
    if not app_controller_path.exists():
        pytest.skip(f"{app_controller_path} not found")
    report = audit_file(app_controller_path)
    fastapi_sites = [f for f in report.findings if f.category == "BOUNDARY_FASTAPI"]
    assert len(fastapi_sites) >= 10, (
        f"Phase 7 regression: expected at least 10 BOUNDARY_FASTAPI sites in "
        f"src/app_controller.py, got {len(fastapi_sites)}. The known sites "
        f"must remain classified after heuristic tightening."
    )
    src = app_controller_path.read_text(encoding="utf-8")
    for site in fastapi_sites[:3]:
        lines = src.split("\n")
        line_num = site.line
        window = "\n".join(lines[max(0, line_num - 5):line_num + 5])
        assert "HTTPException" in window or "Result[" in window, (
            f"Phase 7 regression: site at app_controller.py:{line_num} "
            f"classified BOUNDARY_FASTAPI but window doesn't contain "
            f"HTTPException or Result["
        )


def test_phase7_migrated_sites_no_longer_silent_swallow():
    # L242/L256/L5064/L5093 must not be INTERNAL_SILENT_SWALLOW after Phase 7.
    app_controller_path = ROOT / "src" / "app_controller.py"
    if not app_controller_path.exists():
        pytest.skip(f"{app_controller_path} not found")
    report = audit_file(app_controller_path)
    for f in report.findings:
        if f.line in (242, 256, 5064, 5093):
            assert f.category != "INTERNAL_SILENT_SWALLOW", (
                f"Phase 7 regression: L{f.line} should not be "
                f"INTERNAL_SILENT_SWALLOW after migration; got {f.category}"
            )
