#!/usr/bin/env python3
"""
selftest — proves node_core is PORTABLE and FAITHFUL: imports with no path hacks / no third-party deps,
runs a toy skill through BOTH the S036 engine and the S045 loop (adopt + refuse), and asserts the
vendored engine is byte-identical to the sealed canonical core. Run:  python3 -m node_core.selftest
"""
from __future__ import annotations
import hashlib, os, sys

from node_core import Registry, learn, learn_unique, CORE_SHA16  # package import, no path hacks
from node_core import (SkillManifest, DomainSkill, SkillRegistry,      # the frozen S041 skill ABI
                       ConformanceError, check_conformance)


def _sha16(p):
    return hashlib.sha256(open(p, "rb").read()).hexdigest()[:16]


# ── a toy SKILL (domain code lives OUTSIDE the core, supplied by the consumer) ──
class ToyDomain:
    """Primitives over plain ints — stands in for any domain a consumer would write."""
    def primitives(self):
        return [({"op": "double"}, lambda d: d * 2),
                ({"op": "inc"}, lambda d: d + 1),
                ({"op": "square"}, lambda d: d * d)]


def main():
    R = {}

    # 1) S036 engine: learn the unique op from examples; refuse when ambiguous
    reg = Registry()
    res = learn("f", ToyDomain(), [(2, 4), (3, 6)], [5, 10], reg)          # only 'double' fits
    R["engine_adopts_double"] = res.get("adopted") and reg.value("f", 7) == 14
    amb = learn("g", ToyDomain(), [(2, 4)], [5, 10], Registry())           # double & square both fit 2->4
    R["engine_refuses_ambiguous"] = not amb.get("adopted")

    # 2) S045 loop: same shape, explicit candidate list + metric
    cands = ["double", "inc", "square"]
    metric = lambda d, c: {"double": d * 2, "inc": d + 1, "square": d * d}[c]
    lu = learn_unique(cands, metric, [(2, 4), (3, 6)], [5, 10])
    R["loop_adopts_double"] = lu.get("adopted") and lu.get("comp") == "double"
    lu_amb = learn_unique(cands, metric, [(2, 4)], [5, 10])
    R["loop_refuses_ambiguous"] = not lu_amb.get("adopted")

    # 3) the frozen S041 skill ABI: build a TOY skill through it, conformance-check, route, reject bad
    man = SkillManifest(name="toy", description="doubles and increments plain ints",
                        when_to_use="double increment toy integer")
    toy_tools = {  # each tool: right-or-refuse fn + its own independent oracle + a worked case
        "dbl": {"fn": lambda d: {"value": d["x"] * 2}, "oracle": lambda d: d["x"] * 2,
                "cases": [({"x": 3}, {"value": 6})]},
        "inc": {"fn": lambda d: {"value": d["x"] + 1}, "oracle": lambda d: d["x"] + 1,
                "cases": [({"x": 3}, {"value": 4})]},
    }
    sk = DomainSkill(man, toy_tools)                                   # conformance-checks on build
    R["abi_builds_conformant_skill"] = sk.run("dbl", {"x": 5}) == {"value": 10}
    R["abi_hardens_garbage"] = "refuse" in sk.run("dbl", 42) and "refuse" in sk.run("nope", {"x": 1})
    # harden must catch ANY raise, for ANY input, on BOTH paths: the tool fn raising, AND the
    # tool RETURNING an object that raises while harden inspects it (audit KILL 2026-06-29 + re-audit)
    from node_core import harden as _harden
    class _Weird(BaseException):
        pass
    def _boom(_inp):
        raise _Weird("not an Exception")
    class _HostileOut:                                       # __class__ raises -> breaks isinstance()
        @property
        def __class__(self):
            raise _Weird("__class__ raised during result inspection")
    def _returns_hostile(_inp):
        return _HostileOut()
    R["abi_harden_catches_raise_in_fn"] = "refuse" in _harden(_boom)({"x": 1})
    R["abi_harden_catches_raise_in_result_inspection"] = "refuse" in _harden(_returns_hostile)({"x": 1})
    R["abi_harden_catches_hostile_input"] = "refuse" in _harden(lambda i: {"value": 1})(_HostileOut())
    reg = SkillRegistry(); reg.register(sk)
    R["abi_routes_on_metadata"] = reg.route("please double this") == [("toy", 1)]
    bad_ok, bad_v = check_conformance(man, {"broken": {"fn": lambda d: {"value": 1}, "cases": []}})
    R["abi_rejects_nonconformant"] = (not bad_ok) and len(bad_v) > 0   # missing oracle (F003) + no case
    try:
        DomainSkill(man, {"broken": {"fn": lambda d: 1, "oracle": lambda d: 1, "cases": [({}, 1)]}})
        R["abi_fails_loud_on_bad"] = False                            # should have raised (P4)
    except ConformanceError:
        R["abi_fails_loud_on_bad"] = True

    # 4) portability proofs: byte-identical to sealed core; stdlib-only; no domain code in the core/ABI
    here = os.path.dirname(os.path.abspath(__file__))
    R["engine_sha_matches_sealed"] = _sha16(os.path.join(here, "engine.py")) == CORE_SHA16
    src = (open(os.path.join(here, "engine.py")).read() + open(os.path.join(here, "loop.py")).read()
           + open(os.path.join(here, "skill_abi.py")).read())
    R["no_third_party_imports"] = all(x not in src for x in ("import numpy", "import pandas", "import torch", "requests"))
    R["no_domain_literals_in_core"] = not any(w in src.lower() for w in
                                              ("accounting", "ledger", "depreciat", "iris", "cooking", "pence"))

    R["PORTABLE"] = all(R.values())
    print("\n".join(f"  {k}: {v}" for k, v in R.items()))
    print(f"\nnode_core PORTABLE: {R['PORTABLE']}")
    return R


if __name__ == "__main__":
    sys.exit(0 if main()["PORTABLE"] else 1)
