#!/usr/bin/env python3
from __future__ import annotations

import ast
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
brain_path = ROOT / "ai" / "brain_server.py"
practice_path = ROOT / "services" / "design_academy_practice_service.py"
source = brain_path.read_text(encoding="utf-8")
tree = ast.parse(source, filename=str(brain_path))

wanted_assignments = {
    "ACADEMY_PRACTICE_JSON_SCHEMA",
    "ACADEMY_REQUIRED_TOP_LEVEL_KEYS",
}
wanted_classes = {"AcademyStructuredOutputError"}
wanted_functions = {
    "_academy_candidate_object",
    "_academy_validate_shape",
    "canonicalise_academy_answer",
}
selected = []
for node in tree.body:
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        names = {
            alias.name.split(".")[0]
            for alias in node.names
        }
        if names & {"ast", "hashlib", "json", "re"}:
            selected.append(node)
    elif isinstance(node, ast.Assign):
        targets = {
            target.id
            for target in node.targets
            if isinstance(target, ast.Name)
        }
        if targets & wanted_assignments:
            selected.append(node)
    elif isinstance(node, ast.ClassDef) and node.name in wanted_classes:
        selected.append(node)
    elif isinstance(node, ast.FunctionDef) and node.name in wanted_functions:
        selected.append(node)

namespace: dict[str, object] = {}
module = ast.Module(body=selected, type_ignores=[])
exec(compile(module, str(brain_path), "exec"), namespace)
canonicalise = namespace["canonicalise_academy_answer"]
error_type = namespace["AcademyStructuredOutputError"]
schema = namespace["ACADEMY_PRACTICE_JSON_SCHEMA"]
required_keys = namespace["ACADEMY_REQUIRED_TOP_LEVEL_KEYS"]

valid = {
    "understanding": {
        "summary": "A sufficiently detailed harmless summary that explains a repeatable parameter architecture method and measurable acceptance criteria.",
        "required_inputs": ["dimensions"],
        "method": ["define parameters", "apply constraints", "validate outputs"],
        "acceptance_criteria": ["valid solid", "repeatable dimensions"],
    },
    "practice_project": {
        "title": "Desk organiser",
        "goal": "Create a harmless parametric organiser with measurable dimensions.",
        "inputs": ["width"],
        "steps": ["define", "build", "validate"],
        "outputs": ["editable model"],
        "acceptance_criteria": ["valid", "measured"],
    },
    "failure_diagnosis": {
        "failure": "Missing acceptance criteria",
        "cause": "The design brief omitted measurable validation requirements.",
        "correction": "Add explicit dimensional and geometry validation checks.",
    },
    "boundary_cases": {
        "minimum": "Small valid width",
        "nominal": "Normal design width",
        "maximum": "Largest valid width",
        "invalid": "Negative width rejected",
    },
    "provenance_review": {
        "licence_status": "unknown",
        "decision": "quarantine",
        "commercial_use": "blocked",
        "reason": "Unknown licensing cannot support safe commercial release.",
    },
    "safety_gate": {
        "risk_class": "low",
        "action": "continue",
        "forge_execution_allowed": False,
        "reason": "This remains knowledge practice and cannot unlock Forge.",
    },
    "limitations": ["No physical test", "No production release"],
}

strict = canonicalise(json.dumps(valid))
if strict["repaired"] is not False or strict["method"] != "strict_json":
    raise SystemExit("STOP: Strict JSON was not accepted directly")
json.loads(strict["canonical"])

trailing = json.dumps(valid, separators=(",", ":"))
trailing = trailing[:-1] + ",}"
fixed = canonicalise(trailing)
if fixed["repaired"] is not True:
    raise SystemExit("STOP: Trailing comma was not conservatively repaired")
json.loads(fixed["canonical"])

bare = json.dumps(valid, separators=(",", ":")).replace(
    '"limitations":',
    'limitations:',
    1,
)
fixed_bare = canonicalise(bare)
if fixed_bare["repaired"] is not True:
    raise SystemExit("STOP: Bare property key was not repaired")
json.loads(fixed_bare["canonical"])

single_quoted = repr(valid)
fixed_literal = canonicalise(single_quoted)
if fixed_literal["repaired"] is not True:
    raise SystemExit("STOP: Python-style literal was not repaired")
json.loads(fixed_literal["canonical"])

invalid = dict(valid)
invalid.pop("safety_gate")
try:
    canonicalise(json.dumps(invalid))
except error_type:
    pass
else:
    raise SystemExit("STOP: Missing safety_gate was incorrectly accepted")

unsafe = json.loads(json.dumps(valid))
unsafe["safety_gate"]["forge_execution_allowed"] = True
try:
    canonicalise(json.dumps(unsafe))
except error_type:
    pass
else:
    raise SystemExit("STOP: Unsafe Forge flag was incorrectly accepted")

if schema.get("type") != "object":
    raise SystemExit("STOP: Academy JSON schema is invalid")
if set(schema.get("required") or []) != set(required_keys):
    raise SystemExit("STOP: Academy schema required keys differ")

practice_source = practice_path.read_text(encoding="utf-8")
for required_text in (
    'payload.get("output_contract") != "academy_structured_json_v2"',
    'payload.get("structured_output_schema") is not True',
    'payload.get("canonical_json_gate") is not True',
    '"academy structured output invalid"',
    '"http 502"',
    'raw_model_answer.txt',
):
    if required_text not in practice_source:
        raise SystemExit(f"STOP: Missing practice control: {required_text}")

if 'self.path not in {"/ask", "/academy/practice/ask"}' not in source:
    raise SystemExit("STOP: Normal /ask route was not preserved")
if 'format_override=ACADEMY_PRACTICE_JSON_SCHEMA' not in source:
    raise SystemExit("STOP: Ollama JSON schema was not connected")

print(json.dumps({
    "ok": True,
    "output_contract": "academy_structured_json_v2",
    "structured_output_schema": True,
    "canonical_json_gate": True,
    "raw_answer_audited": True,
    "normal_ask_preserved": True,
    "invalid_syntax_retryable": True,
    "repairs_tested": [
        "trailing_comma",
        "bare_property_key",
        "python_literal",
    ],
    "missing_content_invented": False,
    "unsafe_forge_flag_blocked": True,
}, indent=2))
