"""NEOva engine — canonical encoding, domain-separated hashing, merkle.

These constructions are DEFINED by SEAM v0.6 (law) and pinned by the golden
vectors (v0.1/v0.1.1) and the reference oracle. Byte-exactness is enforced by
tests that recompute every vector and cross-check against the imported oracle.
"""

from __future__ import annotations

import hashlib
import json

DOMAINS = ("NEOVA:ENVELOPE:V1", "NEOVA:ORDERED_LEAF:V1", "NEOVA:NODE:V1",
           "NEOVA:EMPTY:V1", "NEOVA:BUCKET:V1", "NEOVA:BUCKET_ENTRY:V1",
           "NEOVA:SENTINEL:V1", "NEOVA:SCOPE_LEAF:V1", "NEOVA:REPLAY:V1",
           "NEOVA:MANIFEST:V1", "NEOVA:SEGHDR:V1", "NEOVA:GENESIS:V1",
           "NEOVA:ERASEAL:V1", "NEOVA:QWS:V1", "NEOVA:KEY:V1",
           "NEOTRUST:WITNESS_SCOPE:V1")

MIN_KEY, MAX_KEY = b"\x00" * 32, b"\xff" * 32


def canonical(obj) -> bytes:
    return json.dumps(obj, sort_keys=True, separators=(",", ":"),
                      ensure_ascii=False).encode()


def H(domain: str, *parts) -> bytes:
    h = hashlib.sha256()
    h.update(domain.encode() + b"\x00")
    for p in parts:
        b = p if isinstance(p, bytes) else canonical(p)
        h.update(len(b).to_bytes(4, "big") + b)
    return h.digest()


def hx(b: bytes) -> str:
    return b.hex()


def _pair(a: bytes, b: bytes) -> bytes:
    return H("NEOVA:NODE:V1", a, b)


def merkle_root(leaves: list) -> bytes:
    if not leaves:
        return H("NEOVA:EMPTY:V1")
    lvl = list(leaves)
    while len(lvl) > 1:
        nxt = [_pair(lvl[i], lvl[i + 1]) for i in range(0, len(lvl) - 1, 2)]
        if len(lvl) % 2:
            nxt.append(lvl[-1])          # odd node promoted (deterministic)
        lvl = nxt
    return lvl[0]


def merkle_path(leaves: list, idx: int) -> list:
    path, lvl, i = [], list(leaves), idx
    while len(lvl) > 1:
        if i % 2 == 0:
            if i + 1 < len(lvl):
                path.append(("R", lvl[i + 1]))
        else:
            path.append(("L", lvl[i - 1]))
        nxt = [_pair(lvl[j], lvl[j + 1]) for j in range(0, len(lvl) - 1, 2)]
        if len(lvl) % 2:
            nxt.append(lvl[-1])
        i //= 2
        lvl = nxt
    return path


def merkle_verify(leaf: bytes, path: list, root: bytes) -> bool:
    h = leaf
    for side, sib in path:
        h = _pair(h, sib) if side == "R" else _pair(sib, h)
    return h == root


def scope_key(network_id, target_era, target_segment, role, node_id, kind) -> bytes:
    return H("NEOTRUST:WITNESS_SCOPE:V1", network_id, target_era,
             target_segment, role, node_id, kind)


def ordered_leaf(global_position, object_root, sk, namespace_id) -> bytes:
    return H("NEOVA:ORDERED_LEAF:V1", global_position, object_root, sk,
             namespace_id)
