#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
مِسبار — أداة البرهان السيادية لنَماي (النسخة الخفيفة)
Misbar Lite — the NAMAI sovereign prover.

الفكرة: الأصل لا يغادر جهتكم أبدًا. مِسبار يعمل داخل بيئتكم، يبصم الأصل
ويقيس أداءه، ثم يُنتج «شهادة قدرة» صغيرة (كيلوبايتات من التجزئات والقياسات)
هي وحدها ما يُودَع في الرصيد الوطني.

The asset never leaves your perimeter. Misbar runs inside your environment,
fingerprints the asset and measures it, and emits a small Capability
Certificate — hashes and metrics only — which is all that ever travels.

خصائص مقصودة — deliberate properties:
  · ملف واحد، مكتبة بايثون القياسية فقط — zero dependencies, fully auditable
  · يعمل معزولًا عن الإنترنت (--out) ويُنقل يدويًا — air-gap friendly
  · الشهادة مربوطة برمز الإيداع عبر HMAC-SHA256 — tamper-evident offline

الاستخدام — usage:
  python3 misbar.py fingerprint <path> --ref NR-2026-0001 --token nmt_xxx
  python3 misbar.py probe --ref NR-2026-0001 --token nmt_xxx \\
          --cmd "python3 eval.py" --metrics metrics.json
  python3 misbar.py interface --ref NR-2026-0001 --token nmt_xxx --schema openapi.json
  خيارات: --server https://namai.sa   --out cert.json   --no-send
"""
import argparse, hashlib, hmac, json, os, subprocess, sys, time, platform
import urllib.request, urllib.error

TOOL = "misbar-lite/1.0"
DEFAULT_SERVER = "https://namai.sa"

# ── Canonical JSON: مطابق تمامًا لخادم نَماي — must match the server byte-for-byte ──
def canon(v):
    if isinstance(v, dict):
        items = sorted(v.items(), key=lambda kv: str(kv[0]))
        return "{" + ",".join(json.dumps(str(k), ensure_ascii=False) + ":" + canon(x) for k, x in items) + "}"
    if isinstance(v, (list, tuple)):
        return "[" + ",".join(canon(x) for x in v) + "]"
    if isinstance(v, bool) or v is None:
        return json.dumps(v)
    if isinstance(v, int):
        return str(v)
    if isinstance(v, float):
        s = f"{v:.6F}".rstrip("0").rstrip(".")
        return s if s else "0"
    return json.dumps(str(v), ensure_ascii=False).replace("\\/", "/")

def sha256_file(path, buf=1 << 20):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while True:
            b = f.read(buf)
            if not b:
                break
            h.update(b)
    return h.hexdigest()

def tree_digest(root):
    """بصمة شجرية حتمية: تجزئة (المسار النسبي + تجزئة المحتوى) لكل ملف، مرتّبة.
       Deterministic tree digest over (relative path, content hash), sorted."""
    root = os.path.abspath(root)
    entries, total = [], 0
    if os.path.isfile(root):
        entries.append((os.path.basename(root), sha256_file(root), os.path.getsize(root)))
        total = os.path.getsize(root)
    else:
        for dirpath, dirnames, filenames in os.walk(root):
            dirnames[:] = sorted(d for d in dirnames if d not in (".git", "__pycache__", ".venv", "node_modules"))
            for fn in sorted(filenames):
                p = os.path.join(dirpath, fn)
                rel = os.path.relpath(p, root).replace(os.sep, "/")
                try:
                    sz = os.path.getsize(p)
                    entries.append((rel, sha256_file(p), sz))
                    total += sz
                except (OSError, PermissionError):
                    continue
    h = hashlib.sha256()
    for rel, dg, _ in entries:
        h.update(rel.encode("utf-8")); h.update(b"\x00"); h.update(dg.encode()); h.update(b"\x00")
    manifest = [{"path": r, "sha256": d, "bytes": s} for r, d, s in entries[:40]]
    return h.hexdigest(), len(entries), total, manifest

def finish(cert, token, args):
    body = dict(cert)
    cert["auth"] = hmac.new(token.encode(), canon(body).encode(), hashlib.sha256).hexdigest()
    out = json.dumps(cert, ensure_ascii=False, indent=2)
    if args.out:
        with open(args.out, "w", encoding="utf-8") as f:
            f.write(out)
        print(f"✦ كُتبت الشهادة — certificate written: {args.out}")
    if args.no_send:
        print("لم تُرسل (وضع العزل). انقلوها وألصقوها في صفحة جواز الأصل.")
        print("Not sent (air-gap mode). Carry the file and paste it on the Asset Passport page.")
        return
    payload = json.dumps({"ref": args.ref, "token": token, "cert": cert}).encode()
    req = urllib.request.Request(args.server.rstrip("/") + "/api/evidence.php",
                                 data=payload, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=30) as r:
            d = json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        try:
            d = json.loads(e.read().decode())
        except Exception:
            d = {"ok": False, "error": f"http_{e.code}"}
    except Exception as e:
        print(f"✗ تعذّر الاتصال — network error: {e}\nاستخدموا --out cert.json --no-send ثم ألصقوا الشهادة يدويًا.")
        sys.exit(2)
    if d.get("ok"):
        m = d.get("mizan", {})
        print(f"✦ قُبل البرهان — evidence accepted.")
        print(f"  cert  : {d.get('cert_hash','')[:24]}…")
        print(f"  chain : event #{d.get('chain_seq')} — الأصل الآن جزء من خاتم سجلّ")
        print(f"  mizan : ARL المُثبَت {m.get('arl_earned')} · score {m.get('score')}")
        print(f"  الجواز: {args.server.rstrip('/')}/a/{args.ref}")
    else:
        print(f"✗ رُفضت الشهادة — rejected: {d.get('error')}")
        sys.exit(1)

def base_cert(kind, args):
    return {
        "v": 1, "kind": kind, "ref": args.ref, "tool": TOOL,
        "issued_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
        "env": {"os": platform.system().lower(), "py": platform.python_version()},
    }

def main():
    ap = argparse.ArgumentParser(description="مِسبار — NAMAI sovereign prover (lite)")
    sub = ap.add_subparsers(dest="command", required=True)
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("--ref", required=True, help="مرجع الأصل NR-YYYY-####")
    common.add_argument("--token", required=True, help="رمز الإيداع nmt_…")
    common.add_argument("--server", default=DEFAULT_SERVER)
    common.add_argument("--out", help="كتابة الشهادة إلى ملف")
    common.add_argument("--no-send", action="store_true", help="وضع العزل — لا إرسال")

    f = sub.add_parser("fingerprint", parents=[common], help="بصمة الأصل — ARL 2")
    f.add_argument("path", help="مسار الأصل: ملف الأوزان أو مجلد المشروع")

    p = sub.add_parser("probe", parents=[common], help="برهان الأداء — ARL 3")
    p.add_argument("--cmd", dest="eval_cmd", help="أمر التقييم الذي يكتب ملف القياسات")
    p.add_argument("--metrics", required=True, help="ملف JSON: {\"accuracy\":0.93,…}")

    i = sub.add_parser("interface", parents=[common], help="جاهزية الواجهة — ARL 4")
    i.add_argument("--schema", required=True, help="ملف مواصفة الواجهة (OpenAPI/JSON)")

    args = ap.parse_args()

    if args.command == "fingerprint":
        print(f"مِسبار: بصم {args.path} …")
        digest, files, size, manifest = tree_digest(args.path)
        cert = base_cert("fingerprint", args)
        cert.update({"digest": digest, "files": files, "bytes": size, "manifest": manifest})
        print(f"  digest: {digest}\n  files : {files} · bytes: {size}")
        finish(cert, args.token, args)

    elif args.command == "probe":
        dur = None
        if args.eval_cmd:
            print(f"مِسبار: تشغيل التقييم — running: {args.eval_cmd}")
            t0 = time.time()
            rc = subprocess.call(args.eval_cmd, shell=True)
            dur = round(time.time() - t0, 2)
            if rc != 0:
                print(f"✗ أمر التقييم فشل (rc={rc})"); sys.exit(1)
        with open(args.metrics, encoding="utf-8") as fh:
            metrics = json.load(fh)
        metrics = {str(k): float(v) for k, v in metrics.items() if isinstance(v, (int, float))}
        if not metrics:
            print("✗ لا قياسات رقمية في الملف — no numeric metrics"); sys.exit(1)
        cert = base_cert("probe", args)
        cert.update({"metrics": metrics})
        if dur is not None:
            cert["duration_s"] = dur
        print("  metrics:", ", ".join(f"{k}={v}" for k, v in list(metrics.items())[:6]))
        finish(cert, args.token, args)

    elif args.command == "interface":
        h = sha256_file(args.schema)
        cert = base_cert("interface", args)
        cert.update({"schema_hash": h, "schema_file": os.path.basename(args.schema)})
        print(f"  schema_hash: {h}")
        finish(cert, args.token, args)

if __name__ == "__main__":
    main()
