//! Key encoding for the EAV / VAE redb tables.
//!
//! Keys are big-endian, length-prefixed byte concatenations so redb's natural
//! lex ordering coincides with the semantic ordering we want for prefix
//! scans (by entity, by (attr, value), etc.).

use ontoref_types::{canonical, Hlc, Value};

use crate::error::{Error, Result};

pub(crate) fn hlc_be(hlc: Hlc) -> [u8; 12] {
    let mut out = [0u8; 12];
    out[..8].copy_from_slice(&hlc.physical.to_be_bytes());
    out[8..].copy_from_slice(&hlc.logical.to_be_bytes());
    out
}

pub(crate) fn hlc_from_be(bytes: &[u8; 12]) -> Hlc {
    let mut physical = [0u8; 8];
    physical.copy_from_slice(&bytes[..8]);
    let mut logical = [0u8; 4];
    logical.copy_from_slice(&bytes[8..]);
    Hlc::new(u64::from_be_bytes(physical), u32::from_be_bytes(logical))
}

pub(crate) fn value_hash(value: &Value) -> [u8; 32] {
    let bytes = canonical::encode(value).expect("canonical encoding never fails for owned types");
    *blake3::hash(&bytes).as_bytes()
}

fn push_len_str(buf: &mut Vec<u8>, s: &str) -> Result<()> {
    let len = u16::try_from(s.len()).map_err(|_| Error::IdentifierTooLong(s.len()))?;
    buf.extend_from_slice(&len.to_be_bytes());
    buf.extend_from_slice(s.as_bytes());
    Ok(())
}

pub(crate) fn eav_key(entity: &str, attr: &str, tx: Hlc) -> Result<Vec<u8>> {
    let mut key = Vec::with_capacity(2 + entity.len() + 2 + attr.len() + 12);
    push_len_str(&mut key, entity)?;
    push_len_str(&mut key, attr)?;
    key.extend_from_slice(&hlc_be(tx));
    Ok(key)
}

pub(crate) fn eav_prefix_entity(entity: &str) -> Result<Vec<u8>> {
    let mut key = Vec::with_capacity(2 + entity.len());
    push_len_str(&mut key, entity)?;
    Ok(key)
}

pub(crate) fn eav_prefix_entity_attr(entity: &str, attr: &str) -> Result<Vec<u8>> {
    let mut key = Vec::with_capacity(2 + entity.len() + 2 + attr.len());
    push_len_str(&mut key, entity)?;
    push_len_str(&mut key, attr)?;
    Ok(key)
}

pub(crate) fn vae_key(attr: &str, value_h: [u8; 32], entity: &str, tx: Hlc) -> Result<Vec<u8>> {
    let mut key = Vec::with_capacity(2 + attr.len() + 32 + 2 + entity.len() + 12);
    push_len_str(&mut key, attr)?;
    key.extend_from_slice(&value_h);
    push_len_str(&mut key, entity)?;
    key.extend_from_slice(&hlc_be(tx));
    Ok(key)
}

pub(crate) fn vae_prefix_attr_value(attr: &str, value_h: [u8; 32]) -> Result<Vec<u8>> {
    let mut key = Vec::with_capacity(2 + attr.len() + 32);
    push_len_str(&mut key, attr)?;
    key.extend_from_slice(&value_h);
    Ok(key)
}

/// Parse the (attr, tx_time) suffix of an EAV key whose entity prefix is
/// `entity`.
pub(crate) fn parse_eav_key_attr_tx(key: &[u8], entity: &str) -> Result<(String, Hlc)> {
    let entity_prefix_len = 2 + entity.len();
    if key.len() < entity_prefix_len + 2 + 12 {
        return Err(Error::CorruptKey);
    }
    let after_entity = &key[entity_prefix_len..];
    let attr_len = u16::from_be_bytes([after_entity[0], after_entity[1]]) as usize;
    if after_entity.len() < 2 + attr_len + 12 {
        return Err(Error::CorruptKey);
    }
    let attr_bytes = &after_entity[2..2 + attr_len];
    let attr = std::str::from_utf8(attr_bytes)
        .map_err(|_| Error::CorruptKey)?
        .to_string();
    let mut tx_bytes = [0u8; 12];
    tx_bytes.copy_from_slice(&after_entity[2 + attr_len..2 + attr_len + 12]);
    Ok((attr, hlc_from_be(&tx_bytes)))
}

/// Parse the (entity, tx_time) suffix of a VAE key whose (attr, value_hash)
/// prefix is fixed.
pub(crate) fn parse_vae_key_entity_tx(key: &[u8], prefix_len: usize) -> Result<(String, Hlc)> {
    if key.len() < prefix_len + 2 + 12 {
        return Err(Error::CorruptKey);
    }
    let after = &key[prefix_len..];
    let entity_len = u16::from_be_bytes([after[0], after[1]]) as usize;
    if after.len() < 2 + entity_len + 12 {
        return Err(Error::CorruptKey);
    }
    let entity_bytes = &after[2..2 + entity_len];
    let entity = std::str::from_utf8(entity_bytes)
        .map_err(|_| Error::CorruptKey)?
        .to_string();
    let mut tx_bytes = [0u8; 12];
    tx_bytes.copy_from_slice(&after[2 + entity_len..2 + entity_len + 12]);
    Ok((entity, hlc_from_be(&tx_bytes)))
}

/// Extract the tx_time from an EAV key whose `(entity, attr)` prefix is
/// `entity_attr_prefix`.
pub(crate) fn parse_eav_key_tx_only(key: &[u8], entity_attr_prefix_len: usize) -> Result<Hlc> {
    if key.len() != entity_attr_prefix_len + 12 {
        return Err(Error::CorruptKey);
    }
    let mut tx_bytes = [0u8; 12];
    tx_bytes.copy_from_slice(&key[entity_attr_prefix_len..]);
    Ok(hlc_from_be(&tx_bytes))
}
