#!/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"
service_path = ROOT / "services" / "design_academy_practice_service.py"

brain_tree = ast.parse(brain_path.read_text(encoding="utf-8"))
wanted_assignments = {
    "ACADEMY_OUTPUT_CONTRACT", "ACADEMY_ROUND_SYSTEM_CONTEXTS",
    "ACADEMY_ROUND_JSON_SCHEMAS", "ACADEMY_ROUND_REQUIRED_KEYS",
    "_STRING", "_STRING_8", "_STRING_20", "_STRING_80",
    "_SAFETY_SCHEMA", "_PROVENANCE_SCHEMA",
}
wanted_functions = {
    "_academy_candidate_object", "_academy_validate_shape",
    "canonicalise_academy_answer",
}
selected = []
for node in brain_tree.body:
    if isinstance(node, (ast.Assign, ast.AnnAssign)):
        names = []
        targets = node.targets if isinstance(node, ast.Assign) else [node.target]
        for target in targets:
            if isinstance(target, ast.Name): names.append(target.id)
        if wanted_assignments.intersection(names): selected.append(node)
    elif isinstance(node, ast.ClassDef) and node.name == "AcademyStructuredOutputError":
        selected.append(node)
    elif isinstance(node, ast.FunctionDef) and node.name in wanted_functions:
        selected.append(node)

namespace = {"json": json, "ast": ast, "re": __import__("re"), "hashlib": __import__("hashlib")}
exec(compile(ast.Module(body=selected, type_ignores=[]), str(brain_path), "exec"), namespace)
canonicalise = namespace["canonicalise_academy_answer"]
error_type = namespace["AcademyStructuredOutputError"]
schemas = namespace["ACADEMY_ROUND_JSON_SCHEMAS"]

observed = '{\n  "skill_id": "parametric_cad.parameter-architecture",\n  "name": "Parameter architecture",\n  "domain": "parametric_cad",\n  "risk": "low",\n  "lane": "tool_evidence",\n  "professional_review": false,\n  "round": 1,\n  "focus": "fundamentals and repeatable method",\n  "principles": [\n    "Name parameters by meaning and include units, ranges and dependencies.",\n    "Build from stable datums instead of fragile generated edges.",\n    "Separate user parameters from derived calculations."\n  ],\n  "repeatable_method_steps": [\n    "Define base dimensions as named inputs with explicit units rather than hard-coded values.",\n    "Construct geometry using stable datums to drive derived features through logical relationships.",\n    "Isolate calculation logic so it consumes base parameters and outputs final coordinates."\n  ],\n  "acceptance_criteria": [\n    "Changing one named parameter updates the intended dependent dimension while unrelated geometry remains unchanged.",\n    "The model remains valid when extreme values within the defined ranges are applied."\n  ],\n  "required_inputs": [\n    "Base wall width in meters",\n    "Standard door height in centimeters"\n  ],\n  "safe_knowledge_gate": "All relationships remain knowledge-only mathematical functions of named inputs with no tool execution or release authority.",\n  "limitations": [\n    "This method does not automatically optimise complex layouts requiring global search.",\n    "It requires manual definition of parameter ranges and units for intricate designs."\n  ],\n  "forge_execution_allowed": false\n}'
result = canonicalise(observed, 1)
answer = json.loads(result["canonical"])
expected_keys = {
    "round", "principles", "repeatable_method_steps",
    "acceptance_criteria", "required_inputs", "safe_knowledge_gate",
    "limitations", "forge_execution_allowed",
}
if set(answer) != expected_keys:
    raise SystemExit(f"STOP: Canonical Round 1 keys mismatch: {sorted(answer)}")
if answer["forge_execution_allowed"] is not False:
    raise SystemExit("STOP: Unsafe Forge flag accepted")
if len(answer["repeatable_method_steps"]) != 3:
    raise SystemExit("STOP: Observed method steps were not preserved")
expected_pruned = {"skill_id", "name", "domain", "risk", "lane", "professional_review", "focus"}
if set(result["metadata_pruned"]) != expected_pruned:
    raise SystemExit(f"STOP: Metadata pruning mismatch: {result['metadata_pruned']}")
if "summary" in answer or "safety_gate" in answer or "method" in answer:
    raise SystemExit("STOP: Missing content was invented during canonicalisation")

unsafe = json.loads(observed)
unsafe["forge_execution_allowed"] = True
try:
    canonicalise(json.dumps(unsafe), 1)
except error_type:
    pass
else:
    raise SystemExit("STOP: Unsafe top-level Forge flag was accepted")

unknown = json.loads(observed)
unknown["tool_execution_claimed"] = True
try:
    canonicalise(json.dumps(unknown), 1)
except error_type:
    pass
else:
    raise SystemExit("STOP: Unknown execution claim was silently pruned")

service_source = service_path.read_text(encoding="utf-8")
required = (
    '"academy_round_json_v4"',
    '"academy_round_specific_v3"',
    '"repeatable_method_steps"',
    '"safe_knowledge_gate"',
    'round1_flat_gate',
)
for marker in required:
    if marker not in service_source:
        raise SystemExit(f"STOP: Service marker missing: {marker}")

print(json.dumps({
    "ok": True,
    "output_contract": "academy_round_json_v4",
    "round1_contract": "observed_complete_answer_v1",
    "strict_json_accepted": True,
    "known_metadata_pruned": sorted(expected_pruned),
    "missing_content_invented": False,
    "unsafe_forge_flag_blocked": True,
    "unknown_execution_claim_blocked": True,
    "round2_and_round3_contracts_preserved": set(schemas) == {1, 2, 3},
}, indent=2))
