//! Zero-copy serialization utilities for StemeDB types.
//!
//! This module provides the canonical serialization/deserialization functions
//! for all rkyv-enabled types in the system. **All production code must use
//! these helpers instead of raw `AllocSerializer` usage.**
//!
//! # Design Philosophy
//!
//! Following the "Deep Module" principle, these functions hide the complexity
//! of rkyv's serialization machinery behind simple interfaces.
//!
//! # Example
//!
//! ```
//! use stemedb_core::serde::{serialize, deserialize};
//! use stemedb_core::types::Vote;
//!
//! let vote = Vote {
//!     assertion_hash: [0u8; 32],
//!     agent_id: [1u8; 32],
//!     weight: 0.8,
//!     signature: [2u8; 64],
//!     timestamp: 1000,
//!     source_url: None,
//!     observed_context: None,
//! };
//!
//! // Serialize
//! let bytes = serialize(&vote).expect("serialization failed");
//!
//! // Deserialize
//! let recovered: Vote = deserialize(&bytes).expect("deserialization failed");
//! assert_eq!(vote, recovered);
//! ```
//!
//! # Performance
//!
//! Uses a 4096-byte scratch buffer which is sufficient for most assertions.
//! Larger payloads will cause reallocation but still work correctly.

use rkyv::ser::serializers::AllocSerializer;
use rkyv::ser::Serializer;
use rkyv::validation::validators::DefaultValidator;
use rkyv::{Archive, CheckBytes, Deserialize, Serialize};
use thiserror::Error;

/// Default scratch buffer size for serialization.
///
/// 4KB is sufficient for most assertions. Larger payloads will trigger
/// reallocation but the operation will still succeed.
pub const DEFAULT_SCRATCH_SIZE: usize = 4096;

/// Errors that can occur during serialization/deserialization.
#[derive(Debug, Error)]
pub enum SerdeError {
    /// Failed to serialize the value.
    #[error("Serialization error: {0}")]
    Serialization(String),
    /// Failed to validate or deserialize the archived data.
    #[error("Deserialization error: {0}")]
    Deserialization(String),
}

/// Serialize a value to bytes using rkyv zero-copy serialization.
///
/// This is the canonical way to serialize StemeDB types. All production
/// code should use this instead of raw `AllocSerializer`.
///
/// # Type Requirements
///
/// The type `T` must implement rkyv's `Serialize` trait, which all StemeDB
/// core types do.
///
/// # Example
///
/// ```
/// use stemedb_core::serde::serialize;
/// use stemedb_core::types::Assertion;
/// # use stemedb_core::types::{ObjectValue, LifecycleStage, SourceClass};
///
/// let assertion = Assertion {
///     subject: "test".to_string(),
///     predicate: "is".to_string(),
///     object: ObjectValue::Boolean(true),
///     parent_hash: None,
///     source_hash: [0u8; 32],
///     source_class: SourceClass::Expert,
///     visual_hash: None,
///     epoch: None,
///     source_metadata: None,
///     lifecycle: LifecycleStage::Proposed,
///     signatures: vec![],
///     confidence: 1.0,
///     timestamp: 0,
///     hlc_timestamp: stemedb_core::types::HlcTimestamp::default(),
///     vector: None,
/// };
///
/// let bytes = serialize(&assertion).expect("serialize");
/// assert!(!bytes.is_empty());
/// ```
pub fn serialize<T>(value: &T) -> Result<Vec<u8>, SerdeError>
where
    T: Serialize<AllocSerializer<DEFAULT_SCRATCH_SIZE>>,
{
    let mut serializer = AllocSerializer::<DEFAULT_SCRATCH_SIZE>::default();
    serializer.serialize_value(value).map_err(|e| SerdeError::Serialization(e.to_string()))?;
    Ok(serializer.into_serializer().into_inner().to_vec())
}

/// Deserialize bytes back to a value using rkyv zero-copy deserialization.
///
/// This is the canonical way to deserialize StemeDB types. All production
/// code should use this instead of raw `check_archived_root`.
///
/// # Type Requirements
///
/// The type `T` must implement rkyv's `Archive` and `Deserialize` traits,
/// and its archived form must implement `CheckBytes` for validation.
///
/// # Safety
///
/// This function validates the archived data before deserialization,
/// ensuring memory safety even with untrusted input.
///
/// # Example
///
/// ```
/// use stemedb_core::serde::{serialize, deserialize};
/// use stemedb_core::types::Vote;
///
/// let vote = Vote {
///     assertion_hash: [0u8; 32],
///     agent_id: [1u8; 32],
///     weight: 0.8,
///     signature: [2u8; 64],
///     timestamp: 1000,
///     source_url: None,
///     observed_context: None,
/// };
///
/// let bytes = serialize(&vote).expect("serialize");
/// let recovered: Vote = deserialize(&bytes).expect("deserialize");
/// assert_eq!(vote, recovered);
/// ```
pub fn deserialize<T>(data: &[u8]) -> Result<T, SerdeError>
where
    T: Archive,
    T::Archived: for<'a> CheckBytes<DefaultValidator<'a>> + Deserialize<T, rkyv::Infallible>,
{
    let archived = rkyv::check_archived_root::<T>(data)
        .map_err(|e| SerdeError::Deserialization(e.to_string()))?;

    archived
        .deserialize(&mut rkyv::Infallible)
        .map_err(|e| SerdeError::Deserialization(e.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{
        Assertion, Epoch, HlcTimestamp, LifecycleStage, ObjectValue, SignatureEntry, SourceClass,
        Vote,
    };

    #[test]
    fn test_serialize_deserialize_assertion() {
        let assertion = Assertion {
            subject: "Tesla_Inc".to_string(),
            predicate: "has_revenue".to_string(),
            object: ObjectValue::Number(96.7),
            parent_hash: None,
            source_hash: [0u8; 32],
            source_class: SourceClass::Clinical,
            visual_hash: Some([1u8; 8]),
            epoch: Some([2u8; 32]),
            source_metadata: None,
            lifecycle: LifecycleStage::Approved,
            signatures: vec![SignatureEntry {
                agent_id: [2u8; 32],
                signature: [3u8; 64],
                timestamp: 123456789,
                version: 1,
            }],
            confidence: 0.95,
            timestamp: 123456789,
            hlc_timestamp: HlcTimestamp::default(),
            vector: Some(vec![0.1, 0.2, 0.3]),
        };

        let bytes = serialize(&assertion).expect("serialize");
        let recovered: Assertion = deserialize(&bytes).expect("deserialize");
        assert_eq!(assertion, recovered);
    }

    #[test]
    fn test_serialize_deserialize_vote() {
        let vote = Vote {
            assertion_hash: [1u8; 32],
            agent_id: [2u8; 32],
            weight: 0.8,
            signature: [3u8; 64],
            timestamp: 123456789,
            source_url: None,
            observed_context: None,
        };

        let bytes = serialize(&vote).expect("serialize");
        let recovered: Vote = deserialize(&bytes).expect("deserialize");
        assert_eq!(vote, recovered);
    }

    #[test]
    fn test_serialize_deserialize_vote_with_provenance() {
        let vote = Vote {
            assertion_hash: [1u8; 32],
            agent_id: [2u8; 32],
            weight: 0.8,
            signature: [3u8; 64],
            timestamp: 123456789,
            source_url: Some("https://example.com/article".to_string()),
            observed_context: Some(b"The study found that...".to_vec()),
        };

        let bytes = serialize(&vote).expect("serialize");
        let recovered: Vote = deserialize(&bytes).expect("deserialize");
        assert_eq!(vote, recovered);
        assert_eq!(recovered.source_url, Some("https://example.com/article".to_string()));
        assert_eq!(recovered.observed_context, Some(b"The study found that...".to_vec()));
    }

    #[test]
    fn test_serialize_deserialize_vote_with_url_only() {
        let vote = Vote {
            assertion_hash: [1u8; 32],
            agent_id: [2u8; 32],
            weight: 0.8,
            signature: [3u8; 64],
            timestamp: 123456789,
            source_url: Some("https://example.com/article".to_string()),
            observed_context: None,
        };

        let bytes = serialize(&vote).expect("serialize");
        let recovered: Vote = deserialize(&bytes).expect("deserialize");
        assert_eq!(vote, recovered);
        assert_eq!(recovered.source_url, Some("https://example.com/article".to_string()));
        assert!(recovered.observed_context.is_none());
    }

    #[test]
    fn test_serialize_deserialize_vote_with_context_only() {
        let vote = Vote {
            assertion_hash: [1u8; 32],
            agent_id: [2u8; 32],
            weight: 0.8,
            signature: [3u8; 64],
            timestamp: 123456789,
            source_url: None,
            observed_context: Some(b"The study found that...".to_vec()),
        };

        let bytes = serialize(&vote).expect("serialize");
        let recovered: Vote = deserialize(&bytes).expect("deserialize");
        assert_eq!(vote, recovered);
        assert!(recovered.source_url.is_none());
        assert_eq!(recovered.observed_context, Some(b"The study found that...".to_vec()));
    }

    #[test]
    fn test_serialize_deserialize_epoch() {
        let epoch = Epoch {
            id: [1u8; 32],
            name: "Test Epoch".to_string(),
            supersedes: None,
            supersession_type: None,
            start_timestamp: 1000,
            end_timestamp: None,
        };

        let bytes = serialize(&epoch).expect("serialize");
        let recovered: Epoch = deserialize(&bytes).expect("deserialize");
        assert_eq!(epoch, recovered);
    }

    #[test]
    fn test_deserialize_invalid_data() {
        let garbage = vec![0u8, 1, 2, 3, 4, 5];
        let result: Result<Vote, _> = deserialize(&garbage);
        assert!(result.is_err());
    }

    #[test]
    fn test_serialize_empty_assertion() {
        let assertion = Assertion {
            subject: String::new(),
            predicate: String::new(),
            object: ObjectValue::Boolean(false),
            parent_hash: None,
            source_hash: [0u8; 32],
            source_class: SourceClass::Expert,
            visual_hash: None,
            epoch: None,
            source_metadata: None,
            lifecycle: LifecycleStage::Proposed,
            signatures: vec![],
            confidence: 0.0,
            timestamp: 0,
            hlc_timestamp: HlcTimestamp::default(),
            vector: None,
        };

        let bytes = serialize(&assertion).expect("serialize");
        let recovered: Assertion = deserialize(&bytes).expect("deserialize");
        assert_eq!(assertion, recovered);
    }

    #[test]
    fn test_serialize_deserialize_assertion_with_metadata() {
        let metadata = r#"{"journal":"Nature","DOI":"10.1038/xyz","sample_size":1234}"#;

        let assertion = Assertion {
            subject: "Semaglutide".to_string(),
            predicate: "muscle_effect".to_string(),
            object: ObjectValue::Text("significant_loss".to_string()),
            parent_hash: None,
            source_hash: [1u8; 32],
            source_class: SourceClass::Clinical,
            visual_hash: None,
            epoch: None,
            source_metadata: Some(metadata.as_bytes().to_vec()),
            lifecycle: LifecycleStage::Proposed,
            signatures: vec![],
            confidence: 0.85,
            timestamp: 1700000000,
            hlc_timestamp: HlcTimestamp::default(),
            vector: None,
        };

        let bytes = serialize(&assertion).expect("serialize");
        let recovered: Assertion = deserialize(&bytes).expect("deserialize");

        assert_eq!(assertion, recovered);
        assert_eq!(recovered.source_metadata, Some(metadata.as_bytes().to_vec()));
    }

    #[test]
    fn test_serialize_deserialize_assertion_without_metadata() {
        let assertion = Assertion {
            subject: "test".to_string(),
            predicate: "test".to_string(),
            object: ObjectValue::Boolean(true),
            parent_hash: None,
            source_hash: [0u8; 32],
            source_class: SourceClass::Expert,
            visual_hash: None,
            epoch: None,
            source_metadata: None,
            lifecycle: LifecycleStage::Proposed,
            signatures: vec![],
            confidence: 1.0,
            timestamp: 0,
            hlc_timestamp: HlcTimestamp::default(),
            vector: None,
        };

        let bytes = serialize(&assertion).expect("serialize");
        let recovered: Assertion = deserialize(&bytes).expect("deserialize");

        assert_eq!(assertion, recovered);
        assert!(recovered.source_metadata.is_none());
    }
}
