//! Frozen Format v1 per-leaf header (spec §4.5).

use crate::error::StateTreeError;

pub(crate) const LEAF_HEADER_V1_SIZE: usize = 20;
const FORMAT_VERSION_V1: u8 = 0x01;

/// Fixed 20-byte header prepended to every leaf value in the state tree.
///
/// ```text
/// format_version  : u8      — 0x01 for v1
/// namespace       : u8      — 0x01 identity / 0x02 confidential / 0x03 secrets
/// reserved        : [u8; 2]
/// epoch_id        : u32     — MPK rotation epoch at encryption time (big-endian)
/// created_at      : u64     — wall-clock timestamp (big-endian)
/// ciphertext_len  : u32     — ciphertext byte length (big-endian)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct LeafHeaderV1 {
    /// `0x01` for format v1.
    pub format_version: u8,
    /// Namespace tag: `0x01` identity / `0x02` confidential / `0x03` secrets.
    pub namespace: u8,
    /// Reserved for future use — must be zero in v1.
    pub reserved: [u8; 2],
    /// MPK rotation epoch at encryption time (big-endian).
    pub epoch_id: u32,
    /// Wall-clock timestamp (big-endian).
    pub created_at: u64,
    /// Ciphertext byte length (big-endian).
    pub ciphertext_len: u32,
}

impl LeafHeaderV1 {
    /// Create a new v1 leaf header.
    pub fn new(namespace: u8, epoch_id: u32, created_at: u64, ciphertext_len: u32) -> Self {
        Self {
            format_version: FORMAT_VERSION_V1,
            namespace,
            reserved: [0u8; 2],
            epoch_id,
            created_at,
            ciphertext_len,
        }
    }

    /// Serialize to the fixed 20-byte wire format.
    pub fn encode(&self) -> [u8; LEAF_HEADER_V1_SIZE] {
        let mut buf = [0u8; LEAF_HEADER_V1_SIZE];
        buf[0] = self.format_version;
        buf[1] = self.namespace;
        buf[2..4].copy_from_slice(&self.reserved);
        buf[4..8].copy_from_slice(&self.epoch_id.to_be_bytes());
        buf[8..16].copy_from_slice(&self.created_at.to_be_bytes());
        buf[16..20].copy_from_slice(&self.ciphertext_len.to_be_bytes());
        buf
    }

    /// Deserialize from the fixed 20-byte wire format.
    pub fn decode(buf: &[u8]) -> crate::error::Result<Self> {
        if buf.len() < LEAF_HEADER_V1_SIZE {
            return Err(StateTreeError::Serialization(format!(
                "leaf header too short: {} bytes, need {}",
                buf.len(),
                LEAF_HEADER_V1_SIZE,
            )));
        }
        let format_version = buf[0];
        if format_version != FORMAT_VERSION_V1 {
            return Err(StateTreeError::Serialization(format!(
                "unsupported leaf header format version: 0x{format_version:02x}"
            )));
        }
        let namespace = buf[1];
        if crate::key::NamespaceId::from_byte(namespace).is_none() {
            return Err(StateTreeError::Serialization(format!(
                "invalid namespace byte: 0x{namespace:02x}"
            )));
        }
        let reserved = [buf[2], buf[3]];
        if reserved != [0, 0] {
            return Err(StateTreeError::Serialization(
                "reserved bytes must be zero in format v1".into(),
            ));
        }
        let epoch_id = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
        let created_at = u64::from_be_bytes([buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]]);
        let ciphertext_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
        Ok(Self {
            format_version,
            namespace,
            reserved,
            epoch_id,
            created_at,
            ciphertext_len,
        })
    }

    /// Wrap `ciphertext` in the 20-byte v1 header, returning the concatenated
    /// `header || ciphertext` wire payload.
    ///
    /// Validates that `self.ciphertext_len` matches `ciphertext.len()` so a
    /// producer cannot construct a header whose declared length disagrees with
    /// the bytes actually written. Mismatch is caught at the library boundary
    /// instead of surfacing later at decode time on a downstream consumer.
    pub fn encode_with_ciphertext(&self, ciphertext: &[u8]) -> crate::error::Result<Vec<u8>> {
        if ciphertext.len() != self.ciphertext_len as usize {
            return Err(StateTreeError::Serialization(format!(
                "ciphertext_len mismatch: header declares {}, got {} bytes",
                self.ciphertext_len,
                ciphertext.len(),
            )));
        }
        let mut out = Vec::with_capacity(LEAF_HEADER_V1_SIZE + ciphertext.len());
        out.extend_from_slice(&self.encode());
        out.extend_from_slice(ciphertext);
        Ok(out)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encode_decode_roundtrip() {
        let header = LeafHeaderV1::new(0x01, 42, 1_700_000_000, 1024);
        let encoded = header.encode();
        assert_eq!(encoded.len(), LEAF_HEADER_V1_SIZE);
        let decoded = LeafHeaderV1::decode(&encoded).unwrap();
        assert_eq!(header, decoded);
    }

    #[test]
    fn format_version_byte_is_v1() {
        let header = LeafHeaderV1::new(0x02, 0, 0, 0);
        assert_eq!(header.encode()[0], 0x01);
    }

    #[test]
    fn rejects_unsupported_version() {
        let mut buf = LeafHeaderV1::new(0x01, 0, 0, 0).encode();
        buf[0] = 0x02;
        let err = LeafHeaderV1::decode(&buf).unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));
    }

    #[test]
    fn rejects_short_buffer() {
        let err = LeafHeaderV1::decode(&[0x01; 10]).unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));
    }

    #[test]
    fn rejects_invalid_namespace() {
        let mut buf = LeafHeaderV1::new(0x01, 0, 0, 0).encode();
        buf[1] = 0x00;
        let err = LeafHeaderV1::decode(&buf).unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));

        buf[1] = 0x04;
        let err = LeafHeaderV1::decode(&buf).unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));
    }

    #[test]
    fn rejects_nonzero_reserved() {
        let mut buf = LeafHeaderV1::new(0x01, 0, 0, 0).encode();
        buf[2] = 0xFF;
        let err = LeafHeaderV1::decode(&buf).unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));
    }

    #[test]
    fn big_endian_fields() {
        let header = LeafHeaderV1::new(0x03, 0x01020304, 0x0102030405060708, 0x0A0B0C0D);
        let buf = header.encode();
        assert_eq!(&buf[4..8], &[0x01, 0x02, 0x03, 0x04]);
        assert_eq!(&buf[8..16], &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
        assert_eq!(&buf[16..20], &[0x0A, 0x0B, 0x0C, 0x0D]);
    }

    #[test]
    fn encode_with_ciphertext_concats_header_and_bytes() {
        let ciphertext = b"opaque-ciphertext-bytes";
        let header = LeafHeaderV1::new(0x02, 7, 1_700_000_000, ciphertext.len() as u32);
        let wire = header.encode_with_ciphertext(ciphertext).unwrap();

        assert_eq!(wire.len(), LEAF_HEADER_V1_SIZE + ciphertext.len());
        assert_eq!(&wire[..LEAF_HEADER_V1_SIZE], &header.encode()[..]);
        assert_eq!(&wire[LEAF_HEADER_V1_SIZE..], ciphertext);

        let decoded = LeafHeaderV1::decode(&wire).unwrap();
        assert_eq!(decoded, header);
    }

    #[test]
    fn encode_with_ciphertext_rejects_len_mismatch() {
        let header = LeafHeaderV1::new(0x01, 0, 0, 10);

        let err = header.encode_with_ciphertext(b"too-short").unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));

        let err = header.encode_with_ciphertext(b"way-too-long-ciphertext").unwrap_err();
        assert!(matches!(err, StateTreeError::Serialization(_)));
    }
}
