//! Helper functions for Aphoria API handlers.

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

use crate::dto::aphoria::{
    AcknowledgmentDto, ConflictTraceDto, ConflictingSourceDto, FindingDto, ObservationDto,
    ObservationValueDto, PolicySourceDto,
};

/// Convert an Aphoria ConflictResult to a FindingDto.
pub fn conflict_result_to_dto(c: &aphoria::ConflictResult) -> FindingDto {
    let verdict = match c.verdict {
        aphoria::Verdict::Block => "BLOCK",
        aphoria::Verdict::Flag => "FLAG",
        aphoria::Verdict::Pass => "PASS",
        aphoria::Verdict::Ack => "ACK",
        aphoria::Verdict::Drift => "DRIFT",
    };

    let conflicts: Vec<ConflictingSourceDto> = c
        .conflicts
        .iter()
        .map(|src| ConflictingSourceDto {
            path: src.path.clone(),
            source_class: format!("{:?}", src.source_class),
            value: format!("{:?}", src.value),
            citation: src.rfc_citation.clone(),
            policy_source: src.policy_source.as_ref().map(|ps| PolicySourceDto {
                pack_name: ps.pack_name.clone(),
                pack_version: ps.pack_version.clone(),
                issuer_hex: ps.issuer_hex.clone(),
            }),
        })
        .collect();

    let acknowledgment = c.acknowledged.as_ref().map(|ack| AcknowledgmentDto {
        timestamp: ack.timestamp.clone(),
        by: ack.by.clone(),
        reason: ack.reason.clone(),
    });

    let trace = c.trace.as_ref().map(|t| ConflictTraceDto {
        code_claim: t.code_claim.clone(),
        authority_match: t.authority_match.clone(),
        authority_tier: t.authority_tier.clone(),
        resolution: t.resolution.clone(),
    });

    FindingDto {
        concept_path: c.claim.concept_path.clone(),
        predicate: c.claim.predicate.clone(),
        code_value: format!("{:?}", c.claim.value),
        file: c.claim.file.clone(),
        line: c.claim.line,
        conflict_score: c.conflict_score,
        verdict: verdict.to_string(),
        conflicts,
        acknowledgment,
        trace,
    }
}

/// Convert an ObservationDto to an internal Assertion.
pub fn observation_dto_to_assertion(
    dto: &ObservationDto,
    project_id: &str,
    team_id: Option<&str>,
    now: u64,
) -> Result<Assertion, String> {
    // Convert object value
    let object = match &dto.object {
        ObservationValueDto::Text(s) => ObjectValue::Text(s.clone()),
        ObservationValueDto::Number(n) => ObjectValue::Number(*n),
        ObservationValueDto::Boolean(b) => ObjectValue::Boolean(*b),
        ObservationValueDto::Reference(e) => ObjectValue::Reference(e.clone()),
    };

    // Decode source hash
    let source_hash_bytes =
        hex::decode(&dto.source_hash).map_err(|e| format!("Invalid source_hash: {}", e))?;
    if source_hash_bytes.len() != 32 {
        return Err(format!("source_hash must be 32 bytes, got {}", source_hash_bytes.len()));
    }
    let mut source_hash = [0u8; 32];
    source_hash.copy_from_slice(&source_hash_bytes);

    // Convert signatures
    let mut signatures = Vec::new();
    for sig_dto in &dto.signatures {
        let agent_id_bytes =
            hex::decode(&sig_dto.agent_id).map_err(|e| format!("Invalid agent_id: {}", e))?;
        if agent_id_bytes.len() != 32 {
            return Err(format!("agent_id must be 32 bytes, got {}", agent_id_bytes.len()));
        }
        let mut agent_id = [0u8; 32];
        agent_id.copy_from_slice(&agent_id_bytes);

        let signature_bytes =
            hex::decode(&sig_dto.signature).map_err(|e| format!("Invalid signature: {}", e))?;
        if signature_bytes.len() != 64 {
            return Err(format!("signature must be 64 bytes, got {}", signature_bytes.len()));
        }
        let mut signature = [0u8; 64];
        signature.copy_from_slice(&signature_bytes);

        signatures.push(SignatureEntry {
            agent_id,
            signature,
            timestamp: sig_dto.timestamp,
            version: sig_dto.version,
        });
    }

    // Augment source metadata with project/team info
    let source_metadata = {
        let mut metadata: serde_json::Value = dto
            .source_metadata
            .as_ref()
            .and_then(|s| serde_json::from_str(s).ok())
            .unwrap_or(serde_json::json!({}));

        if let Some(obj) = metadata.as_object_mut() {
            obj.insert("aphoria_project_id".to_string(), serde_json::json!(project_id));
            if let Some(team) = team_id {
                obj.insert("aphoria_team_id".to_string(), serde_json::json!(team));
            }
            obj.insert("aphoria_received_at".to_string(), serde_json::json!(now));
        }

        serde_json::to_vec(&metadata).ok()
    };

    Ok(Assertion {
        subject: dto.subject.clone(),
        predicate: dto.predicate.clone(),
        object,
        parent_hash: None,
        source_hash,
        source_class: SourceClass::Community, // Tier 4 for observations
        visual_hash: None,
        epoch: None,
        source_metadata,
        lifecycle: LifecycleStage::Approved,
        signatures,
        confidence: dto.confidence,
        timestamp: dto.timestamp,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    })
}

/// Compute the content hash of an assertion.
pub fn compute_assertion_hash(assertion: &Assertion) -> [u8; 32] {
    let mut hasher = Hasher::new();
    hasher.update(assertion.subject.as_bytes());
    hasher.update(assertion.predicate.as_bytes());
    hasher.update(format!("{:?}", assertion.object).as_bytes());
    hasher.update(&assertion.source_hash);
    hasher.update(&[assertion.source_class.tier()]);
    *hasher.finalize().as_bytes()
}
