"""NEO gateway admission — the cross-language bincode replication (§2.5, guardrail #1/#2 wire).

To submit `batch_root` to NEO's existing `/v1/submit`, the Python client must produce a gateway-signed
`AdmissionRecord` whose signature NEO's Rust verifier accepts. NEO computes:

    signing_hash = SHA3-256( bincode( GatewaySigningPayloadV1 { domain, record: AdmissionRecordNoSignature } ) )
    signature    = Ed25519_sign(gateway_key, signing_hash)

`bincode` here is **FIXINT, little-endian** — NEO's `canonical_bytes` starts from `bincode::DefaultOptions`
but OVERRIDES it with `.with_fixint_encoding().with_little_endian().reject_trailing_bytes()` (verified in
neo-core/serialization.rs). So integers are FIXED width (u64 = 8 bytes LE, u32 = 4, u8 = 1) and a String/Vec
length prefix is a u64 (8 bytes LE) — NOT varint. (Confirming this fixint-vs-varint point via golden vectors
was exactly Cowork's flag #1; the first golden vector caught the wrong varint assumption immediately.) A
`[u8; 32]` is a fixed array — raw 32 bytes, NO length prefix (unlike `Vec<u8>`). The layout below is pinned to
NEO's struct field order and GATED by `tests/golden/vectors/neo_admission_v1.json`.
"""

from __future__ import annotations

import hashlib
from dataclasses import dataclass

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

GATEWAY_ADMISSION_DOMAIN = "NEO::GATEWAY::ADMISSION::V1"


def _u8(n: int) -> bytes:
    if not 0 <= n <= 0xFF:
        raise ValueError("u8 out of range")
    return bytes([n])


def _u32_le(n: int) -> bytes:
    return int(n).to_bytes(4, "little")


def _u64_le(n: int) -> bytes:
    return int(n).to_bytes(8, "little")


def _len_prefix(n: int) -> bytes:
    """bincode fixint length prefix for String/Vec = a u64 (8 bytes LE)."""
    return _u64_le(n)


def _arr32(b: bytes) -> bytes:
    if len(b) != 32:
        raise ValueError("expected 32 bytes (a [u8; 32] is fixed-size — no length prefix)")
    return b  # bincode serializes a fixed array as the raw bytes, no length prefix


@dataclass(frozen=True)
class NeoAdmissionRecord:
    chain_id: bytes
    gateway_id: bytes
    gateway_epoch: int
    gateway_sequence: int
    target_height: int
    earliest_valid_height: int
    expiry_height: int
    admission_index: int
    priority_class: int
    event_commitment_hash: bytes
    idempotency_key: bytes
    namespace: bytes
    account_id: bytes
    policy_snapshot_id: bytes

    def signing_preimage(self) -> bytes:
        """bincode(GatewaySigningPayloadV1 { domain: String, record: AdmissionRecordNoSignature })."""
        domain = GATEWAY_ADMISSION_DOMAIN.encode("utf-8")
        out = bytearray()
        out += _len_prefix(len(domain))  # String length prefix (u64 LE, fixint)
        out += domain
        out += _arr32(self.chain_id)
        out += _arr32(self.gateway_id)
        out += _u64_le(self.gateway_epoch)
        out += _u64_le(self.gateway_sequence)
        out += _u64_le(self.target_height)
        out += _u64_le(self.earliest_valid_height)
        out += _u64_le(self.expiry_height)
        out += _u32_le(self.admission_index)
        out += _u8(self.priority_class)
        out += _arr32(self.event_commitment_hash)
        out += _arr32(self.idempotency_key)
        out += _arr32(self.namespace)
        out += _arr32(self.account_id)
        out += _arr32(self.policy_snapshot_id)
        return bytes(out)

    def signing_hash(self) -> bytes:
        return hashlib.sha3_256(self.signing_preimage()).digest()


def gateway_public_key(seed: bytes) -> bytes:
    """The raw 32-byte Ed25519 public key for a gateway seed (matches NEO's `Ed25519Signer::from_seed`)."""
    from cryptography.hazmat.primitives import serialization

    sk = Ed25519PrivateKey.from_private_bytes(seed)
    return sk.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)


def sign_admission(seed: bytes, record: NeoAdmissionRecord) -> bytes:
    """Ed25519-sign the admission signing hash with the gateway seed (deterministic; matches NEO byte-for-byte)."""
    sk = Ed25519PrivateKey.from_private_bytes(seed)
    return sk.sign(record.signing_hash())
