//! Paste record format.
//!
//! Binary format (no external serialization deps):
//!   version: u8
//!   created_at: u64 (big-endian)
//!   ttl_secs: u64 (big-endian)
//!   content: [u8] (remaining bytes)

use tesseras_dht::sha2::{Digest, Sha256};

/// Maximum paste size: 64 KiB.
pub const MAX_PASTE_SIZE: usize = 64 * 1024;

/// Current format version.
const FORMAT_VERSION: u8 = 1;

/// Header size: version(1) + created_at(8) + ttl(8) = 17.
const HEADER_SIZE: usize = 17;

/// A paste record stored locally and replicated via the DHT.
#[derive(Debug, Clone)]
pub struct Paste {
    pub version: u8,
    pub content: Vec<u8>,
    pub created_at: u64,
    pub ttl_secs: u64,
}

impl Paste {
    /// Create a new paste with the current timestamp.
    pub fn new(content: Vec<u8>, ttl_secs: u64) -> Self {
        let created_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        Paste {
            version: FORMAT_VERSION,
            content,
            created_at,
            ttl_secs,
        }
    }

    /// Compute the SHA-256 content-addressed key (32 bytes).
    pub fn content_key(content: &[u8]) -> [u8; 32] {
        let mut h = Sha256::new();
        h.update(content);
        h.finalize().into()
    }

    /// Serialize to bytes.
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(HEADER_SIZE + self.content.len());
        buf.push(self.version);
        buf.extend_from_slice(&self.created_at.to_be_bytes());
        buf.extend_from_slice(&self.ttl_secs.to_be_bytes());
        buf.extend_from_slice(&self.content);
        buf
    }

    /// Deserialize from bytes.
    pub fn from_bytes(data: &[u8]) -> Option<Self> {
        if data.len() < HEADER_SIZE {
            return None;
        }
        let version = data[0];
        let created_at = u64::from_be_bytes(data[1..9].try_into().ok()?);
        let ttl_secs = u64::from_be_bytes(data[9..17].try_into().ok()?);
        let content = data[HEADER_SIZE..].to_vec();
        Some(Paste {
            version,
            content,
            created_at,
            ttl_secs,
        })
    }

    /// Whether this paste has expired.
    pub fn is_expired(&self) -> bool {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        now > self.created_at.saturating_add(self.ttl_secs)
    }
}

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

    #[test]
    fn round_trip() {
        let paste = Paste::new(b"hello world".to_vec(), 3600);
        let bytes = paste.to_bytes();
        let decoded = Paste::from_bytes(&bytes).unwrap();
        assert_eq!(decoded.content, b"hello world");
        assert_eq!(decoded.ttl_secs, 3600);
        assert_eq!(decoded.version, FORMAT_VERSION);
    }

    #[test]
    fn content_key_deterministic() {
        let k1 = Paste::content_key(b"test");
        let k2 = Paste::content_key(b"test");
        assert_eq!(k1, k2);
    }

    #[test]
    fn content_key_differs() {
        let k1 = Paste::content_key(b"aaa");
        let k2 = Paste::content_key(b"bbb");
        assert_ne!(k1, k2);
    }

    #[test]
    fn too_short_fails() {
        assert!(Paste::from_bytes(&[0u8; 5]).is_none());
    }

    #[test]
    fn empty_content() {
        let paste = Paste::new(Vec::new(), 60);
        let bytes = paste.to_bytes();
        let decoded = Paste::from_bytes(&bytes).unwrap();
        assert!(decoded.content.is_empty());
    }
}
