//! Conversion functions between core types and DTOs.

use stemedb_core::types::{Assertion, LifecycleStage, ObjectValue, SignatureEntry, SourceClass};

use super::enums::{LifecycleDto, ObjectValueDto, SignatureDto, SourceClassDto};
use super::requests::{CreateAssertionRequest, HlcTimestampDto};

/// Convert an Assertion to a CreateAssertionRequest.
///
/// This is an infallible conversion that hex-encodes all binary fields.
/// Includes timestamp and HLC timestamp for v2 signature verification.
pub fn assertion_to_request(assertion: &Assertion) -> CreateAssertionRequest {
    CreateAssertionRequest {
        subject: assertion.subject.clone(),
        predicate: assertion.predicate.clone(),
        object: object_value_to_dto(&assertion.object),
        confidence: assertion.confidence,
        signatures: assertion.signatures.iter().map(signature_to_dto).collect(),
        source_hash: hex::encode(assertion.source_hash),
        parent_hash: assertion.parent_hash.map(hex::encode),
        source_class: Some(source_class_to_dto(assertion.source_class)),
        lifecycle: Some(lifecycle_to_dto(assertion.lifecycle)),
        source_metadata: assertion
            .source_metadata
            .as_ref()
            .map(|b| String::from_utf8_lossy(b).into_owned()),
        // Include timestamps for v2 signature verification
        timestamp: Some(assertion.timestamp),
        hlc_timestamp: Some(HlcTimestampDto {
            time_ntp64: assertion.hlc_timestamp.time_ntp64,
            node_id: hex::encode(assertion.hlc_timestamp.node_id),
        }),
    }
}

/// Convert ObjectValue to DTO.
pub fn object_value_to_dto(value: &ObjectValue) -> ObjectValueDto {
    match value {
        ObjectValue::Text(s) => ObjectValueDto::Text(s.clone()),
        ObjectValue::Number(n) => ObjectValueDto::Number(*n),
        ObjectValue::Boolean(b) => ObjectValueDto::Boolean(*b),
        ObjectValue::Reference(r) => ObjectValueDto::Reference(r.clone()),
    }
}

/// Convert SignatureEntry to DTO.
pub fn signature_to_dto(entry: &SignatureEntry) -> SignatureDto {
    SignatureDto {
        agent_id: hex::encode(entry.agent_id),
        signature: hex::encode(entry.signature),
        timestamp: entry.timestamp,
        version: Some(entry.version),
    }
}

/// Convert SourceClass to DTO.
pub fn source_class_to_dto(sc: SourceClass) -> SourceClassDto {
    match sc {
        SourceClass::Regulatory => SourceClassDto::Regulatory,
        SourceClass::Clinical => SourceClassDto::Clinical,
        SourceClass::Observational => SourceClassDto::Observational,
        SourceClass::Expert => SourceClassDto::Expert,
        SourceClass::Community => SourceClassDto::Community,
        SourceClass::Anecdotal => SourceClassDto::Anecdotal,
    }
}

/// Convert LifecycleStage to DTO.
pub fn lifecycle_to_dto(stage: LifecycleStage) -> LifecycleDto {
    match stage {
        LifecycleStage::Proposed => LifecycleDto::Proposed,
        LifecycleStage::UnderReview => LifecycleDto::UnderReview,
        LifecycleStage::Approved => LifecycleDto::Approved,
        LifecycleStage::Deprecated => LifecycleDto::Deprecated,
        LifecycleStage::Rejected => LifecycleDto::Rejected,
    }
}

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

    #[test]
    fn test_assertion_to_request_basic() {
        let assertion = Assertion {
            subject: "Semaglutide:Type2Diabetes".to_string(),
            predicate: "hba1c_change_percent".to_string(),
            object: ObjectValue::Number(-1.2),
            parent_hash: None,
            source_hash: [0u8; 32],
            source_class: SourceClass::Regulatory,
            visual_hash: None,
            epoch: None,
            source_metadata: None,
            lifecycle: LifecycleStage::Approved,
            signatures: vec![SignatureEntry {
                agent_id: [1u8; 32],
                signature: [2u8; 64],
                timestamp: 1234567890,
                version: 2,
            }],
            confidence: 0.95,
            timestamp: 1234567890,
            hlc_timestamp: HlcTimestamp::default(),
            vector: None,
        };

        let request = assertion_to_request(&assertion);

        assert_eq!(request.subject, "Semaglutide:Type2Diabetes");
        assert_eq!(request.predicate, "hba1c_change_percent");
        assert_eq!(request.confidence, 0.95);
        assert_eq!(request.signatures.len(), 1);
        assert_eq!(request.signatures[0].version, Some(2));
        assert!(matches!(request.source_class, Some(SourceClassDto::Regulatory)));
        assert!(matches!(request.lifecycle, Some(LifecycleDto::Approved)));
    }

    #[test]
    fn test_object_value_conversion() {
        assert!(matches!(
            object_value_to_dto(&ObjectValue::Text("test".to_string())),
            ObjectValueDto::Text(s) if s == "test"
        ));
        assert!(matches!(
            object_value_to_dto(&ObjectValue::Number(42.5)),
            ObjectValueDto::Number(n) if (n - 42.5).abs() < f64::EPSILON
        ));
        assert!(matches!(
            object_value_to_dto(&ObjectValue::Boolean(true)),
            ObjectValueDto::Boolean(true)
        ));
        assert!(matches!(
            object_value_to_dto(&ObjectValue::Reference("ref".to_string())),
            ObjectValueDto::Reference(r) if r == "ref"
        ));
    }
}
