"""
RBSP Binary Signature Encoding Protocol.

Defines the compact binary format for natural signatures, including
serialization, deserialization, and wire format specifications.

Wire format (fixed 64 bytes):
    Bytes  0-3:   Magic number (0x52425350 = "RBSP")
    Bytes  4:     Version (uint8)
    Bytes  5:     Flags (uint8)
    Bytes  6-7:   Size class (uint16, big-endian)
    Bytes  8-15:  Content hash (uint64, big-endian)
    Bytes 16-23:  Semantic hash (uint64, big-endian)
    Bytes 24-31:  Structural hash (uint64, big-endian)
    Bytes 32-39:  Timestamp (double, big-endian IEEE 754)
    Bytes 40-55:  Reserved / extension (16 bytes, zeroed)
    Bytes 56-63:  Checksum of bytes 0-55 (uint64, big-endian)
"""

from __future__ import annotations

import struct
import time
from dataclasses import dataclass, field
from typing import Optional

# --- Constants -----------------------------------------------------------

MAGIC = 0x52425350  # ASCII "RBSP"
VERSION = 1
SIGNATURE_SIZE = 64  # bytes
HEADER_FMT = ">IBBHQQQ d 16s Q"  # struct format for the wire layout
# Breakdown:
#   I   = magic (4)
#   B   = version (1)
#   B   = flags (1)
#   H   = size_class (2)
#   Q   = content_hash (8)
#   Q   = semantic_hash (8)
#   Q   = structural_hash (8)
#   d   = timestamp (8)
#   16s = reserved (16)
#   Q   = checksum (8)
# Total = 4+1+1+2+8+8+8+8+16+8 = 64

# Flag bits
FLAG_HAS_SEMANTIC = 0x01
FLAG_HAS_STRUCTURAL = 0x02
FLAG_COUNTING = 0x04  # entry participates in counting bloom


def _compute_checksum(data: bytes) -> int:
    """Compute a fast 64-bit checksum over *data* using the RBSP hash core.

    This uses a Fowler-Noll-Vo-style rolling hash with RBSP-specific primes
    to produce a 64-bit integrity digest.  It is NOT cryptographic.
    """
    h: int = 0xCBF29CE484222325  # FNV offset basis
    for b in data:
        h ^= b
        h = (h * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF  # FNV prime, masked to 64 bits
    return h


# --- Data classes --------------------------------------------------------

@dataclass(frozen=True, slots=True)
class NaturalSignature:
    """Immutable representation of an RBSP natural signature.

    A natural signature is a deterministic, content-derived fingerprint that
    captures content identity, semantic similarity, and structural shape in a
    compact fixed-size binary format.

    Attributes:
        content_hash:    Fast custom hash of the raw content bytes.
        semantic_hash:   LSH-based semantic fingerprint (n-gram / MinHash).
        structural_hash: Hash of structural elements (indentation, brackets, keywords).
        timestamp:       Creation time as a POSIX float.
        size_class:      Logarithmic size bucket (0-65535).
        flags:           Bit-field indicating which optional hashes are populated.
    """

    content_hash: int
    semantic_hash: int = 0
    structural_hash: int = 0
    timestamp: float = field(default_factory=time.time)
    size_class: int = 0
    flags: int = 0

    # -- Serialization ----------------------------------------------------

    def to_bytes(self) -> bytes:
        """Encode this signature into the 64-byte RBSP wire format."""
        reserved = b"\x00" * 16
        payload = struct.pack(
            HEADER_FMT,
            MAGIC,
            VERSION,
            self.flags,
            self.size_class & 0xFFFF,
            self.content_hash & 0xFFFFFFFFFFFFFFFF,
            self.semantic_hash & 0xFFFFFFFFFFFFFFFF,
            self.structural_hash & 0xFFFFFFFFFFFFFFFF,
            self.timestamp,
            reserved,
            0,  # placeholder checksum
        )
        # Compute checksum over everything except the last 8 bytes
        checksum = _compute_checksum(payload[:56])
        return payload[:56] + struct.pack(">Q", checksum)

    @classmethod
    def from_bytes(cls, data: bytes) -> "NaturalSignature":
        """Decode a 64-byte buffer into a ``NaturalSignature``.

        Raises:
            ValueError: If the data is too short, has a bad magic number, or
                fails the integrity checksum.
        """
        if len(data) < SIGNATURE_SIZE:
            raise ValueError(
                f"Expected {SIGNATURE_SIZE} bytes, got {len(data)}"
            )

        (
            magic,
            version,
            flags,
            size_class,
            content_hash,
            semantic_hash,
            structural_hash,
            timestamp,
            _reserved,
            checksum,
        ) = struct.unpack(HEADER_FMT, data[:SIGNATURE_SIZE])

        if magic != MAGIC:
            raise ValueError(f"Bad magic: 0x{magic:08X} (expected 0x{MAGIC:08X})")

        expected_checksum = _compute_checksum(data[:56])
        if checksum != expected_checksum:
            raise ValueError("Checksum mismatch — data may be corrupted")

        return cls(
            content_hash=content_hash,
            semantic_hash=semantic_hash,
            structural_hash=structural_hash,
            timestamp=timestamp,
            size_class=size_class,
            flags=flags,
        )

    # -- Convenience ------------------------------------------------------

    def hex(self) -> str:
        """Return a hex-encoded string of the wire format."""
        return self.to_bytes().hex()

    @classmethod
    def from_hex(cls, hexstr: str) -> "NaturalSignature":
        """Decode from a hex string."""
        return cls.from_bytes(bytes.fromhex(hexstr))

    def __repr__(self) -> str:
        return (
            f"NaturalSignature(content=0x{self.content_hash:016X}, "
            f"semantic=0x{self.semantic_hash:016X}, "
            f"structural=0x{self.structural_hash:016X}, "
            f"size_class={self.size_class})"
        )


def compute_size_class(length: int) -> int:
    """Map a content length to a logarithmic size bucket (0-65535).

    Bucket 0 is empty content.  Each subsequent bucket roughly doubles:
    bucket *k* covers lengths in ``[2^(k-1), 2^k)``.  The maximum bucket
    is clamped to 65535 (uint16 max).
    """
    if length <= 0:
        return 0
    # bit_length gives floor(log2(length)) + 1
    bucket = length.bit_length()
    return min(bucket, 0xFFFF)
