#!/usr/bin/env python3
"""Local structural/governance checks. NOT official OrgDef schema conformance.

Draft and adopted states can both validate. This checks record references and
structural properties but does not independently authenticate human signatures.\nA matching approved_by string is NOT evidence of GitHub account-holder approval.\nAdoption and staffing require a separately verified principal GitHub PR review.
"""
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent
STATES = {"proposed", "adopted"}
TYPES = {"reports_to", "coordinates_with", "validates_for"}

class ValidationError(ValueError):
    pass

def require(ok, message):
    if not ok:
        raise ValidationError(message)

def valid_text(value):
    return isinstance(value, str) and bool(value.strip())

def validate(org, root=ROOT):
    require(isinstance(org, dict), "organization must be an object")
    require(org.get("type") == "orgdef:Organization", "organization type")
    require(org.get("orgdef") == "1.0.0" and org.get("catdef") == "1.4", "envelope version")
    require(valid_text(org.get("version")), "version required")
    require(org.get("status") in STATES, "status must be proposed or adopted")
    positions = org.get("items", [])
    require(isinstance(positions, list) and len(positions) > 1, "positions missing")
    ids = [p.get("id") for p in positions]
    require(all(valid_text(x) for x in ids) and len(set(ids)) == len(ids), "duplicate or empty seat IDs")
    by_id = {p["id"]: p for p in positions}
    require("principal" in by_id and by_id["principal"].get("status") == "staffed", "human principal required")
    require(all(p.get("type") == "orgdef:Position" and valid_text(p.get("description")) for p in positions), "position type/description")
    divs = org.get("divisions", [])
    require(isinstance(divs, list) and all(valid_text(d.get("id")) for d in divs), "divisions invalid")
    div_ids = [d["id"] for d in divs]
    require(set(div_ids) >= {"labs", "institute"} and len(div_ids) == len(set(div_ids)), "missing/duplicate divisions")
    for p in positions:
        require(p.get("division", "shared") in set(div_ids) | {"shared"}, "unknown division")
        status = p.get("status")
        require(status in {"vacant", "staffed"}, "invalid seat status")
        if status == "vacant":
            require("incumbent" not in p and "appointment_record" not in p, "vacant seat has incumbent/appointment")
        elif p["id"] != "principal":
            require(valid_text(p.get("incumbent")), "staffed seat lacks incumbent")
            record = p.get("appointment_record")
            require(valid_text(record), "staffed seat lacks appointment record")
            path = Path(record)
            require(not path.is_absolute() and ".." not in path.parts, "unsafe appointment path")
            full = root.parent / path
            require(full.is_file(), "appointment record missing: " + record)
            appointment = json.loads(full.read_text(encoding="utf-8"))
            require(appointment.get("seat") == p["id"] and appointment.get("approved_by") == "principal"
                    and valid_text(appointment.get("approved_at")) and valid_text(appointment.get("session_binding")),
                    "appointment record incomplete/inconsistent")
    if org["status"] == "adopted":
        record = org.get("adoption_record")
        require(valid_text(record), "adopted org needs adoption record")
        p = Path(record)
        require(not p.is_absolute() and ".." not in p.parts, "unsafe adoption path")
        full = root.parent / p
        require(full.is_file(), "adoption record missing")
        adoption = json.loads(full.read_text(encoding="utf-8"))
        require(adoption.get("version") == org["version"] and adoption.get("approved_by") == "principal"
                and valid_text(adoption.get("approved_at")) and valid_text(adoption.get("review_memo")),
                "adoption record incomplete/inconsistent")
    else:
        require(not org.get("adoption_record"), "proposed org cannot claim adopted record")
    edges = org.get("relationships", [])
    require(isinstance(edges, list), "relationships must be list")
    parents = {p: [] for p in ids}
    for edge in edges:
        src, dst, typ = edge.get("from"), edge.get("to"), edge.get("type")
        require(src in by_id and dst in by_id, "dangling edge")
        require(typ in TYPES and src != dst, "invalid/self relationship")
        if typ == "reports_to":
            parents[src].append(dst)
    require(not parents["principal"], "principal may not report to another seat")
    def ancestors(node, seen=None):
        if seen is None: seen = set()
        require(node not in seen, "cycle in reporting graph")
        visiting = seen | {node}
        out = set()
        for manager in parents[node]:
            out.add(manager)
            out.update(ancestors(manager, visiting))
        return out
    for node in ids:
        if node != "principal":
            require("principal" in ancestors(node), "seat cannot reach principal")
    review_edges = [e for e in edges if e["type"] == "validates_for"]
    for edge in review_edges:
        reviewer, target = edge["from"], edge["to"]
        ra, ta = ancestors(reviewer), ancestors(target)
        require(reviewer not in ta and target not in ra, "reviewer in target chain")
        require(not (ra & ta - {"principal"}), "reviewer shares target reporting manager")
    require(any(e["from"] == "institute-reviewer" and e["to"] == "labs-implementer" for e in review_edges),
            "missing Institute cross-division review edge")
    require(any(e["from"] == "labs-qa" and e["to"] == "labs-release" for e in review_edges),
            "missing independent release validation")
    for key in ("values", "red_lines"):
        records = org.get(key, [])
        require(isinstance(records, list) and len(records) >= 3
                and all(isinstance(r, dict) and valid_text(r.get("description" if key == "values" else "rule")) for r in records),
                key + " missing meaningful entries")
    # R1: critical disclosures and record locations must not silently disappear.
    limitations = org.get("limitations")
    require(isinstance(limitations, list) and len(limitations) >= 3
            and all(valid_text(x) for x in limitations), "missing/blank governance limitations")
    policy = org.get("publication_policy")
    require(isinstance(policy, dict), "missing publication policy")
    classes = policy.get("authorship_classes")
    require(isinstance(classes, list) and
            set(classes) >= {"founder-authored", "ai-assisted", "ai-authored"} and
            len(classes) == len(set(classes)), "missing authorship classes")
    require(isinstance(policy.get("restricted_data"), list)
            and all(valid_text(x) for x in policy["restricted_data"])
            and len(policy["restricted_data"]) >= 1, "missing publication restrictions")
    require(isinstance(policy.get("required_metadata"), list)
            and all(valid_text(x) for x in policy["required_metadata"])
            and len(policy["required_metadata"]) >= 1, "missing publication metadata")
    record_paths = org.get("records")
    require(isinstance(record_paths, dict)
            and all(valid_text(record_paths.get(k)) for k in ("memos", "appointments", "ratification", "review")),
            "missing governance record conventions")
    require(org.get("history", [{}])[-1].get("version") == org["version"], "version history mismatch")
    require(any("Edsby" in r["rule"] for r in org["red_lines"]), "employer confidentiality red line missing")
    return {"positions": len(positions), "relationships": len(edges), "state": org["status"]}

def main():
    org = json.loads((ROOT / "orgdef.json").read_text(encoding="utf-8"))
    result = validate(org)
    print("LOCAL STRUCTURAL PASS:", result)
    print("UNVERIFIED OrgDef extensions: divisions, division, authority, organization status.")
    print("NOT official OrgDef conformance, authenticated principal approval, or evidence of active seat controls.")

if __name__ == "__main__":
    main()
