//! The binary form of group and object receipts and of a group's receipts together
//! (`extensions/jwp-receipts.md` §2.7).
//!
//! The form is a second spelling of the JSON members, not a second receipt: a reader here
//! yields the members of §2.4.1 and §2.4.2 as JSON, and [`GroupReceipt::from_json`] and
//! [`ObjectReceipt::from_json`] then read them exactly as they read the JSON form, so
//! every rule of §3 applies to both alike. A group's binary form is its canonical bytes
//! (§3 step 3), followed, in the legacy form only, by its parent link; its link hash is
//! computed over the bytes as received. A hybrid group (`moq-jwp:group-sign\x03`,
//! pq-signatures §6.2, read only with `pq_signatures`) is the labelled form's fields, then
//! its ML-DSA-65 key, `sig`, and its ML-DSA-65 signature.
//!
//! What does not read is `malformed` (§2.7.4): at §3 step 1 for the magic, a group, the
//! count and the record's end, at §3 step 4 for an object.

use super::{DeliveryError, GroupReceipt, ObjectLabel, ObjectReceipt, Reason, DOMAIN_GROUP_SIGN, DOMAIN_GROUP_SIGN_LABELLED, DOMAIN_RECEIPT_LINK};
#[cfg(feature = "pq_signatures")]
use super::DOMAIN_GROUP_SIGN_HYBRID;
use serde_json::{json, Map, Value};

/// §2.7.3: the magic of a group's receipts.
pub const MAGIC: &[u8; 4] = b"WJR1";

/// A group receipt and object receipts of its group, as one record (§2.7.3).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GroupReceipts {
    pub group: GroupReceipt,
    pub objects: Vec<ObjectReceipt>,
}

fn malformed(step: u8) -> DeliveryError {
    DeliveryError::at(step, Reason::Malformed)
}

/// A cursor over the bytes a reader was given; every failure is `malformed` at `step`.
struct Cursor<'a> {
    b: &'a [u8],
    at: usize,
    step: u8,
}

impl<'a> Cursor<'a> {
    fn take(&mut self, n: usize) -> Result<&'a [u8], DeliveryError> {
        let end = self.at.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(malformed(self.step))?;
        let s = &self.b[self.at..end];
        self.at = end;
        Ok(s)
    }

    fn u8(&mut self) -> Result<u8, DeliveryError> {
        Ok(self.take(1)?[0])
    }

    fn u32(&mut self) -> Result<u32, DeliveryError> {
        Ok(u32::from_be_bytes(self.take(4)?.try_into().expect("4 bytes")))
    }

    fn u64(&mut self) -> Result<u64, DeliveryError> {
        Ok(u64::from_be_bytes(self.take(8)?.try_into().expect("8 bytes")))
    }

    fn hash(&mut self) -> Result<[u8; 32], DeliveryError> {
        Ok(self.take(32)?.try_into().expect("32 bytes"))
    }

    fn string(&mut self) -> Result<String, DeliveryError> {
        let n = self.u32()? as usize;
        let s = self.take(n)?;
        std::str::from_utf8(s).map(str::to_owned).map_err(|_| malformed(self.step))
    }

    /// A class's signed encoding (energy-measurement §2.7), as the JSON members it names:
    /// none for tag `0`.
    fn class(&mut self, m: &mut Map<String, Value>) -> Result<(), DeliveryError> {
        let tag = self.u8()?;
        let label = match tag {
            0 => return Ok(()),
            1 => "HwShunt",
            2 => "ModelBased",
            3 => "Estimator",
            4 => "OnChipCounter",
            5 => "CalibratedInstrument",
            _ => return Err(malformed(self.step)),
        };
        m.insert("energy_provenance".into(), label.into());
        if tag >= 4 {
            let ppm = self.u32()?;
            let window = self.u64()?;
            m.insert("energy_uncertainty".into(), json!({ "relative_ppm": ppm, "window_us": window }));
            if tag == 4 {
                let f = match self.u8()? {
                    0 => "Unknown",
                    1 => "Off",
                    2 => "On",
                    _ => return Err(malformed(self.step)),
                };
                m.insert("energy_counter_filtering".into(), f.into());
            } else {
                m.insert("energy_calibration_ref".into(), self.string()?.into());
            }
        }
        Ok(())
    }

    fn end(&self) -> Result<(), DeliveryError> {
        if self.at == self.b.len() { Ok(()) } else { Err(malformed(self.step)) }
    }
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

/// §2.7.1: one group receipt's members, read from `c`, with the length of its canonical
/// bytes.
fn read_group(c: &mut Cursor) -> Result<(Value, usize), DeliveryError> {
    let start = c.at;
    let domain = c.take(DOMAIN_GROUP_SIGN.len())?;
    #[cfg(feature = "pq_signatures")]
    let hybrid = domain == DOMAIN_GROUP_SIGN_HYBRID;
    #[cfg(not(feature = "pq_signatures"))]
    let hybrid = false;
    let labelled = if domain == DOMAIN_GROUP_SIGN_LABELLED || hybrid {
        true
    } else if domain == DOMAIN_GROUP_SIGN {
        false
    } else {
        return Err(malformed(c.step));
    };
    let mut m = Map::new();
    m.insert("kind".into(), "group".into());
    m.insert("track_alias".into(), c.u64()?.into());
    m.insert("group_id".into(), c.u64()?.into());
    m.insert("root_hash".into(), hex(&c.hash()?).into());
    m.insert("leaf_count".into(), c.u64()?.into());
    m.insert("joules_total".into(), c.u64()?.into());
    m.insert("group_start".into(), c.string()?.into());
    m.insert("group_end".into(), c.string()?.into());
    m.insert("signer_pubkey".into(), hex(&c.hash()?).into());
    m.insert("signer_id".into(), c.string()?.into());
    let parent = |c: &mut Cursor| -> Result<Value, DeliveryError> {
        let flag = c.u8()?;
        let h = c.hash()?;
        match flag {
            1 => Ok(hex(&h).into()),
            0 if h == [0; 32] => Ok(Value::Null),
            _ => Err(malformed(c.step)),
        }
    };
    let mut label = Map::new();
    let mut signed_parent = Value::Null;
    if labelled {
        c.class(&mut label)?;
        match c.u8()? {
            0 if label.is_empty() => {}
            0 => {
                label.insert("energy_coverage".into(), "Whole".into());
            }
            1 => {
                label.insert("energy_coverage".into(), "Partial".into());
            }
            _ => return Err(malformed(c.step)),
        }
        signed_parent = parent(c)?;
    }
    #[cfg(feature = "pq_signatures")]
    let pq_key = if hybrid { Some(hex(c.take(crate::pq_sig::PQ_KEY_LEN)?)) } else { None };
    m.insert("sig".into(), hex(c.take(64)?).into());
    #[cfg(feature = "pq_signatures")]
    if let Some(k) = pq_key {
        m.insert("pq_key".into(), k.into());
        m.insert("pq_sig".into(), hex(c.take(crate::pq_sig::PQ_SIG_LEN)?).into());
    }
    let canonical = c.at - start;
    m.insert("parent_receipt_hash".into(), if labelled { signed_parent } else { parent(c)? });
    if labelled {
        m.insert("sign_version".into(), if hybrid { 3 } else { 2 }.into());
        m.extend(label);
    }
    Ok((Value::Object(m), canonical))
}

/// §2.7.2: one object receipt's members from its body, with `track_alias` and
/// `group_id` given.
fn read_object_body(c: &mut Cursor, track_alias: u64, group_id: u64) -> Result<Value, DeliveryError> {
    let mut m = Map::new();
    m.insert("kind".into(), "object".into());
    m.insert("track_alias".into(), track_alias.into());
    m.insert("group_id".into(), group_id.into());
    m.insert("object_id".into(), c.u64()?.into());
    m.insert("content_hash".into(), hex(&c.hash()?).into());
    m.insert("joules_micro".into(), c.u64()?.into());
    m.insert("origin".into(), c.string()?.into());
    let n = c.u8()?;
    let siblings: Vec<Value> = (0..n).map(|_| c.hash().map(|h| Value::from(hex(&h)))).collect::<Result<_, _>>()?;
    let direction_bits = c.u64()?;
    let leaf_index = c.u64()?;
    m.insert("merkle_proof".into(), json!({ "siblings": siblings, "direction_bits": direction_bits, "leaf_index": leaf_index }));
    match c.u8()? {
        1 => {}
        2 => {
            m.insert("leaf_version".into(), 2.into());
            c.class(&mut m)?;
        }
        _ => return Err(malformed(c.step)),
    }
    Ok(Value::Object(m))
}

/// A group receipt's binary form, the whole of `b`, as its JSON members.
pub fn group_json(b: &[u8]) -> Result<Value, DeliveryError> {
    let mut c = Cursor { b, at: 0, step: 1 };
    let (g, _) = read_group(&mut c)?;
    c.end()?;
    Ok(g)
}

/// An object receipt's binary form standing alone (`object_bin`), the whole of `b`, as
/// its JSON members.
pub fn object_json(b: &[u8]) -> Result<Value, DeliveryError> {
    let mut c = Cursor { b, at: 0, step: 4 };
    let track_alias = c.u64()?;
    let group_id = c.u64()?;
    let o = read_object_body(&mut c, track_alias, group_id)?;
    c.end()?;
    Ok(o)
}

/// A group's receipts (§2.7.3), the whole of `b`: the group's JSON members, the link hash
/// computed over its canonical bytes as received, and each object's JSON members.
pub fn receipts_json(b: &[u8]) -> Result<(Value, [u8; 32], Vec<Value>), DeliveryError> {
    let mut c = Cursor { b, at: 0, step: 1 };
    if c.take(4)? != MAGIC {
        return Err(malformed(1));
    }
    let start = c.at;
    let (g, canonical) = read_group(&mut c)?;
    let mut h = blake3::Hasher::new();
    h.update(DOMAIN_RECEIPT_LINK);
    h.update(&b[start..start + canonical]);
    let link = *h.finalize().as_bytes();
    let count = c.u32()?;
    let (ta, gid) = (g["track_alias"].as_u64().expect("read"), g["group_id"].as_u64().expect("read"));
    let mut objects = Vec::new();
    for _ in 0..count {
        c.step = 4;
        objects.push(read_object_body(&mut c, ta, gid)?);
    }
    c.step = 1;
    c.end()?;
    Ok((g, link, objects))
}

impl GroupReceipt {
    /// §2.7.1: the canonical bytes, then, in the legacy form, the parent link.
    pub fn to_binary(&self) -> Vec<u8> {
        let mut b = self.canonical_bytes();
        if self.label.is_none() {
            match self.parent_receipt_hash {
                Some(h) => {
                    b.push(1);
                    b.extend_from_slice(&h);
                }
                None => {
                    b.push(0);
                    b.extend_from_slice(&[0; 32]);
                }
            }
        }
        b
    }

    /// Read a group receipt's binary form (§2.7.1, §2.7.4), then its members as §3 step 1
    /// reads them.
    pub fn from_binary(b: &[u8]) -> Result<GroupReceipt, DeliveryError> {
        GroupReceipt::from_json(&group_json(b)?)
    }
}

impl ObjectReceipt {
    /// §2.7.2's `object_body`; `None` for a receipt read with a label that does not read,
    /// which has no binary form.
    pub fn body_binary(&self) -> Option<Vec<u8>> {
        if self.unreadable_label {
            return None;
        }
        let p = &self.merkle_proof;
        let mut b = Vec::with_capacity(80 + self.origin.len() + 32 * p.siblings.len());
        b.extend_from_slice(&self.object_id.to_be_bytes());
        b.extend_from_slice(&self.content_hash);
        b.extend_from_slice(&self.joules_micro.to_be_bytes());
        b.extend_from_slice(&(self.origin.len() as u32).to_be_bytes());
        b.extend_from_slice(self.origin.as_bytes());
        b.push(u8::try_from(p.siblings.len()).ok()?);
        for s in &p.siblings {
            b.extend_from_slice(s);
        }
        b.extend_from_slice(&p.direction_bits.to_be_bytes());
        b.extend_from_slice(&p.leaf_index.to_be_bytes());
        match &self.label {
            None => b.push(1),
            Some(l) => {
                b.push(2);
                l.write_signed(&mut b);
            }
        }
        Some(b)
    }

    /// §2.7.2's `object_bin`: `track_alias`, `group_id`, then the body.
    pub fn to_binary(&self) -> Option<Vec<u8>> {
        let mut b = Vec::new();
        b.extend_from_slice(&self.track_alias.to_be_bytes());
        b.extend_from_slice(&self.group_id.to_be_bytes());
        b.extend_from_slice(&self.body_binary()?);
        Some(b)
    }

    /// Read an object receipt's binary form standing alone, then its members as §3 step 4
    /// reads them.
    pub fn from_binary(b: &[u8]) -> Result<ObjectReceipt, DeliveryError> {
        ObjectReceipt::from_json(&object_json(b)?)
    }
}

impl GroupReceipts {
    /// §2.7.3. `None` where an object has no binary form, is of another group, or there
    /// are more than 2^32 − 1 objects.
    pub fn to_binary(&self) -> Option<Vec<u8>> {
        let mut b = MAGIC.to_vec();
        b.extend_from_slice(&self.group.to_binary());
        b.extend_from_slice(&u32::try_from(self.objects.len()).ok()?.to_be_bytes());
        for o in &self.objects {
            if o.track_alias != self.group.track_alias || o.group_id != self.group.group_id {
                return None;
            }
            b.extend_from_slice(&o.body_binary()?);
        }
        Some(b)
    }

    /// Read a group's receipts (§2.7.3, §2.7.4), then each receipt's members as §3 steps
    /// 1 and 4 read them.
    pub fn from_binary(b: &[u8]) -> Result<GroupReceipts, DeliveryError> {
        let (g, _, objects) = receipts_json(b)?;
        Ok(GroupReceipts {
            group: GroupReceipt::from_json(&g)?,
            objects: objects.iter().map(ObjectReceipt::from_json).collect::<Result<_, _>>()?,
        })
    }
}

impl ObjectLabel {
    /// The label's signed encoding as a byte vector.
    pub fn signed(&self) -> Vec<u8> {
        let mut b = Vec::new();
        self.write_signed(&mut b);
        b
    }
}

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

    #[test]
    fn a_groups_binary_form_is_its_canonical_bytes() {
        let key = ed25519_dalek::SigningKey::from_bytes(&[0x11; 32]);
        let mut b = GroupBuilder::default();
        b.add([1; 32], 0, "o", Some(ObjectLabel::Unmetered));
        b.add([2; 32], 0, "o", None);
        let (g, objects) = b.finish(&key, "p", 3, 9, "2026-10-18T00:00:00Z", "2026-10-18T00:00:01Z", Some([7; 32]), true).unwrap();
        let bin = g.to_binary();
        assert_eq!(bin, g.canonical_bytes(), "labelled: nothing follows");
        assert_eq!(GroupReceipt::from_binary(&bin).unwrap(), g);
        let r = GroupReceipts { group: g.clone(), objects: objects.clone() };
        let rb = r.to_binary().unwrap();
        assert_eq!(GroupReceipts::from_binary(&rb).unwrap(), r);
        let (_, link, _) = receipts_json(&rb).unwrap();
        assert_eq!(link, g.link_hash());
        let ob = objects[0].to_binary().unwrap();
        assert_eq!(ObjectReceipt::from_binary(&ob).unwrap(), objects[0]);
        // Trailing bytes, a short record, another magic.
        let mut long = rb.clone();
        long.push(0);
        assert_eq!(GroupReceipts::from_binary(&long), Err(malformed(1)));
        assert_eq!(GroupReceipts::from_binary(&rb[..rb.len() - 1]), Err(malformed(4)));
        let mut other = rb.clone();
        other[3] = b'2';
        assert_eq!(GroupReceipts::from_binary(&other), Err(malformed(1)));
    }
}
