//! Shared test helpers for the Episteme workspace.
//!
//! Provides [`AssertionBuilder`] and factory functions to eliminate duplicate
//! test assertion construction across crates. Import via:
//!
//! ```rust,ignore
//! use stemedb_core::testing::{AssertionBuilder, test_vote, test_epoch};
//! ```

use crate::types::{
    Assertion, Epoch, HlcTimestamp, LifecycleStage, ObjectValue, SignatureEntry, SourceClass,
    SupersessionType, Vote,
};

/// Builder for constructing test [`Assertion`] instances.
///
/// Every field has a sensible default so callers only override what they need.
///
/// # Examples
///
/// ```rust,ignore
/// // Minimal
/// let a = AssertionBuilder::new().build();
///
/// // Override subject + timestamp
/// let a = AssertionBuilder::new()
///     .subject("Tesla")
///     .timestamp(2000)
///     .build();
///
/// // Full control
/// let a = AssertionBuilder::new()
///     .subject("Tesla")
///     .predicate("revenue")
///     .object_number(96.7)
///     .confidence(0.8)
///     .lifecycle(LifecycleStage::Proposed)
///     .agent_id([5u8; 32])
///     .timestamp(3000)
///     .build();
/// ```
pub struct AssertionBuilder {
    subject: String,
    predicate: String,
    object: ObjectValue,
    parent_hash: Option<[u8; 32]>,
    source_hash: [u8; 32],
    source_class: SourceClass,
    visual_hash: Option<[u8; 8]>,
    epoch: Option<[u8; 32]>,
    source_metadata: Option<Vec<u8>>,
    lifecycle: LifecycleStage,
    signatures: Option<Vec<SignatureEntry>>,
    agent_id: [u8; 32],
    confidence: f32,
    timestamp: u64,
    hlc_timestamp: HlcTimestamp,
    vector: Option<Vec<f32>>,
}

impl Default for AssertionBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl AssertionBuilder {
    /// Create a new builder with sensible test defaults.
    pub fn new() -> Self {
        Self {
            subject: "test_subject".to_string(),
            predicate: "test_predicate".to_string(),
            object: ObjectValue::Number(100.0),
            parent_hash: None,
            source_hash: [0u8; 32],
            source_class: SourceClass::Expert, // Default to middle tier for tests
            visual_hash: None,
            epoch: None,
            source_metadata: None,
            lifecycle: LifecycleStage::Approved,
            signatures: None, // Will use agent_id to build default
            agent_id: [1u8; 32],
            confidence: 0.9,
            timestamp: 1000,
            hlc_timestamp: HlcTimestamp::default(),
            vector: None,
        }
    }

    /// Set the subject.
    pub fn subject(mut self, subject: &str) -> Self {
        self.subject = subject.to_string();
        self
    }

    /// Set the predicate.
    pub fn predicate(mut self, predicate: &str) -> Self {
        self.predicate = predicate.to_string();
        self
    }

    /// Set the object to a numeric value.
    pub fn object_number(mut self, value: f64) -> Self {
        self.object = ObjectValue::Number(value);
        self
    }

    /// Set the object to a text value.
    pub fn object_text(mut self, value: &str) -> Self {
        self.object = ObjectValue::Text(value.to_string());
        self
    }

    /// Set the object to an arbitrary ObjectValue.
    pub fn object(mut self, value: ObjectValue) -> Self {
        self.object = value;
        self
    }

    /// Set the confidence (0.0 to 1.0).
    pub fn confidence(mut self, confidence: f32) -> Self {
        self.confidence = confidence;
        self
    }

    /// Set the timestamp.
    pub fn timestamp(mut self, timestamp: u64) -> Self {
        self.timestamp = timestamp;
        self
    }

    /// Set the HLC timestamp for distributed causal ordering.
    ///
    /// This provides total ordering even with clock skew between nodes.
    /// Most tests can rely on the default (HlcTimestamp::default()).
    pub fn hlc_timestamp(mut self, hlc_timestamp: HlcTimestamp) -> Self {
        self.hlc_timestamp = hlc_timestamp;
        self
    }

    /// Set the lifecycle stage.
    pub fn lifecycle(mut self, lifecycle: LifecycleStage) -> Self {
        self.lifecycle = lifecycle;
        self
    }

    /// Set the agent_id used in the default signature.
    pub fn agent_id(mut self, agent_id: [u8; 32]) -> Self {
        self.agent_id = agent_id;
        self
    }

    /// Set the source hash.
    pub fn source_hash(mut self, hash: [u8; 32]) -> Self {
        self.source_hash = hash;
        self
    }

    /// Set the source class (authority tier).
    pub fn source_class(mut self, source_class: SourceClass) -> Self {
        self.source_class = source_class;
        self
    }

    /// Set the parent hash.
    pub fn parent_hash(mut self, hash: [u8; 32]) -> Self {
        self.parent_hash = Some(hash);
        self
    }

    /// Set the visual hash.
    pub fn visual_hash(mut self, hash: [u8; 8]) -> Self {
        self.visual_hash = Some(hash);
        self
    }

    /// Set the epoch.
    pub fn epoch(mut self, epoch: [u8; 32]) -> Self {
        self.epoch = Some(epoch);
        self
    }

    /// Set the vector embedding.
    pub fn vector(mut self, vector: Vec<f32>) -> Self {
        self.vector = Some(vector);
        self
    }

    /// Set the source metadata from a JSON string.
    /// Stores as bytes for rkyv compatibility.
    pub fn source_metadata_json(mut self, json: &str) -> Self {
        self.source_metadata = Some(json.as_bytes().to_vec());
        self
    }

    /// Set the source metadata as raw bytes.
    pub fn source_metadata(mut self, metadata: Vec<u8>) -> Self {
        self.source_metadata = Some(metadata);
        self
    }

    /// Provide explicit signatures (overrides the default single-signature behavior).
    pub fn signatures(mut self, signatures: Vec<SignatureEntry>) -> Self {
        self.signatures = Some(signatures);
        self
    }

    /// Build the [`Assertion`].
    pub fn build(self) -> Assertion {
        let signatures = self.signatures.unwrap_or_else(|| {
            vec![SignatureEntry {
                agent_id: self.agent_id,
                signature: [2u8; 64],
                timestamp: self.timestamp,
                version: 1, // Default to v1 for backward compatibility
            }]
        });

        Assertion {
            subject: self.subject,
            predicate: self.predicate,
            object: self.object,
            parent_hash: self.parent_hash,
            source_hash: self.source_hash,
            source_class: self.source_class,
            visual_hash: self.visual_hash,
            epoch: self.epoch,
            source_metadata: self.source_metadata,
            lifecycle: self.lifecycle,
            signatures,
            confidence: self.confidence,
            timestamp: self.timestamp,
            hlc_timestamp: self.hlc_timestamp,
            vector: self.vector,
        }
    }
}

/// Create a test [`Vote`] with the given parameters.
pub fn test_vote(
    assertion_hash: [u8; 32],
    agent_id: [u8; 32],
    weight: f32,
    timestamp: u64,
) -> Vote {
    Vote {
        assertion_hash,
        agent_id,
        weight,
        signature: [0u8; 64],
        timestamp,
        source_url: None,
        observed_context: None,
    }
}

/// Create a test [`Epoch`] with defaults.
pub fn test_epoch() -> Epoch {
    Epoch {
        id: [4u8; 32],
        name: "Test Epoch".to_string(),
        supersedes: None,
        supersession_type: None,
        start_timestamp: 1000,
        end_timestamp: None,
    }
}

/// Create a test [`Epoch`] with a supersession relationship.
pub fn test_epoch_with_supersession(
    id: [u8; 32],
    name: &str,
    supersedes: [u8; 32],
    supersession_type: SupersessionType,
) -> Epoch {
    Epoch {
        id,
        name: name.to_string(),
        supersedes: Some(supersedes),
        supersession_type: Some(supersession_type),
        start_timestamp: 1000,
        end_timestamp: None,
    }
}

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

    #[test]
    fn test_builder_defaults() {
        let a = AssertionBuilder::new().build();
        assert_eq!(a.subject, "test_subject");
        assert_eq!(a.predicate, "test_predicate");
        assert_eq!(a.lifecycle, LifecycleStage::Approved);
        assert!((a.confidence - 0.9).abs() < f32::EPSILON);
        assert_eq!(a.timestamp, 1000);
        assert_eq!(a.signatures.len(), 1);
        assert_eq!(a.signatures[0].agent_id, [1u8; 32]);
    }

    #[test]
    fn test_builder_overrides() {
        let a = AssertionBuilder::new()
            .subject("Tesla")
            .predicate("revenue")
            .object_number(96.7)
            .confidence(0.85)
            .timestamp(5000)
            .lifecycle(LifecycleStage::Proposed)
            .agent_id([5u8; 32])
            .build();

        assert_eq!(a.subject, "Tesla");
        assert_eq!(a.predicate, "revenue");
        assert_eq!(a.object, ObjectValue::Number(96.7));
        assert!((a.confidence - 0.85).abs() < f32::EPSILON);
        assert_eq!(a.timestamp, 5000);
        assert_eq!(a.lifecycle, LifecycleStage::Proposed);
        assert_eq!(a.signatures[0].agent_id, [5u8; 32]);
    }

    #[test]
    fn test_builder_custom_signatures() {
        let sigs = vec![
            SignatureEntry {
                agent_id: [10u8; 32],
                signature: [11u8; 64],
                timestamp: 100,
                version: 1,
            },
            SignatureEntry {
                agent_id: [20u8; 32],
                signature: [21u8; 64],
                timestamp: 200,
                version: 1,
            },
        ];

        let a = AssertionBuilder::new().signatures(sigs).build();
        assert_eq!(a.signatures.len(), 2);
        assert_eq!(a.signatures[0].agent_id, [10u8; 32]);
    }

    #[test]
    fn test_builder_text_object() {
        let a = AssertionBuilder::new().object_text("hello").build();
        assert_eq!(a.object, ObjectValue::Text("hello".to_string()));
    }

    #[test]
    fn test_vote_factory() {
        let v = test_vote([1u8; 32], [2u8; 32], 0.8, 3000);
        assert_eq!(v.assertion_hash, [1u8; 32]);
        assert_eq!(v.agent_id, [2u8; 32]);
        assert!((v.weight - 0.8).abs() < f32::EPSILON);
        assert_eq!(v.timestamp, 3000);
    }

    #[test]
    fn test_epoch_factory() {
        let e = test_epoch();
        assert_eq!(e.id, [4u8; 32]);
        assert_eq!(e.name, "Test Epoch");
        assert!(e.supersedes.is_none());
    }
}
