//! Content defense types for spam detection and quality scoring.
//!
//! This module provides types for the Content Defense layer (Phase 7C):
//! - [`ContentQuality`]: Quality metrics for an assertion
//! - [`QuarantineReason`]: Why an assertion was quarantined
//! - [`QuarantineEvent`]: A quarantined assertion awaiting review
//! - [`QuarantineDecision`]: Pass or quarantine decision from content checks

use crate::types::Hash;
use rkyv::{Archive, Deserialize, Serialize};

/// Quality metrics computed for an assertion's content.
///
/// Used by the ContentQualityScorer to determine if content should be
/// quarantined for manual review.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct ContentQuality {
    /// Overall quality score in [0.0, 1.0]. Below threshold triggers quarantine.
    pub score: f32,

    /// Shannon entropy of the combined subject+predicate text.
    /// Low entropy (< 1.5 bits/char) suggests random noise or repetitive spam.
    pub entropy: f32,

    /// Whether the content appears to be structured data (JSON, numbers, URLs).
    /// Structured data gets a quality bonus.
    pub structured: bool,

    /// Whether this assertion is a near-duplicate of existing content.
    /// Set by the similarity index (MinHash + LSH).
    pub duplicate: bool,
}

impl ContentQuality {
    /// Create a new ContentQuality with default values (high quality, non-duplicate).
    pub fn new() -> Self {
        Self { score: 1.0, entropy: 3.0, structured: false, duplicate: false }
    }

    /// Check if this content meets the minimum quality threshold.
    ///
    /// Default threshold is 0.4, below which content is considered low-quality.
    pub fn meets_threshold(&self, threshold: f32) -> bool {
        self.score >= threshold
    }
}

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

/// Reason why an assertion was placed in quarantine.
///
/// Each reason maps to a specific defense mechanism:
/// - `LowQuality`: Failed quality scoring (entropy, length, structure)
/// - `Duplicate`: Near-duplicate detected by MinHash + LSH
/// - `UntrustedHighConfidence`: Untrusted agent with suspiciously high confidence
/// - `PatternMatch`: Matched a known spam/abuse pattern
#[derive(Archive, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[archive(check_bytes)]
pub enum QuarantineReason {
    /// Content failed quality checks (low entropy, too short, etc.).
    LowQuality,

    /// Content is a near-duplicate of existing assertion (Jaccard >= 0.9).
    Duplicate,

    /// Untrusted agent submitted assertion with confidence > 0.8.
    /// Suspicious pattern: new/untrusted agents shouldn't be highly confident.
    UntrustedHighConfidence,

    /// Content matched a known spam or abuse pattern.
    PatternMatch,
}

impl QuarantineReason {
    /// Get a human-readable description of this quarantine reason.
    pub fn description(&self) -> &'static str {
        match self {
            Self::LowQuality => "Content failed quality checks (low entropy or too short)",
            Self::Duplicate => "Near-duplicate of existing assertion detected",
            Self::UntrustedHighConfidence => "Untrusted agent submitted high-confidence assertion",
            Self::PatternMatch => "Content matched known spam or abuse pattern",
        }
    }
}

/// A quarantined assertion awaiting admin review.
///
/// Stored at `\x00QUAR:{timestamp}:{hash_hex}` for time-ordered scanning.
/// The original assertion bytes are preserved for later indexing if approved.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct QuarantineEvent {
    /// Content-addressed hash of the original assertion.
    pub hash: Hash,

    /// The serialized assertion bytes (preserved for approval flow).
    pub assertion_bytes: Vec<u8>,

    /// Why this assertion was quarantined.
    pub reason: QuarantineReason,

    /// Quality metrics at the time of quarantine.
    pub quality: ContentQuality,

    /// Unix timestamp (nanoseconds) when quarantined.
    pub timestamp: u64,

    /// Has an admin reviewed this event?
    pub reviewed: bool,

    /// If reviewed, was it approved (true) or rejected (false)?
    /// None if not yet reviewed.
    pub approved: Option<bool>,

    /// Optional similar assertion hash (for duplicates).
    pub similar_to: Option<Hash>,

    /// Agent ID that submitted the assertion (for audit trail).
    pub agent_id: Option<[u8; 32]>,
}

impl QuarantineEvent {
    /// Create a new quarantine event.
    pub fn new(
        hash: Hash,
        assertion_bytes: Vec<u8>,
        reason: QuarantineReason,
        quality: ContentQuality,
        timestamp: u64,
    ) -> Self {
        Self {
            hash,
            assertion_bytes,
            reason,
            quality,
            timestamp,
            reviewed: false,
            approved: None,
            similar_to: None,
            agent_id: None,
        }
    }

    /// Set the similar assertion hash (for duplicate detection).
    pub fn with_similar_to(mut self, similar: Hash) -> Self {
        self.similar_to = Some(similar);
        self
    }

    /// Set the agent ID for audit trail.
    pub fn with_agent_id(mut self, agent_id: [u8; 32]) -> Self {
        self.agent_id = Some(agent_id);
        self
    }

    /// Mark this event as reviewed with an approval decision.
    pub fn mark_reviewed(&mut self, approved: bool) {
        self.reviewed = true;
        self.approved = Some(approved);
    }

    /// Check if this event is pending review.
    pub fn is_pending(&self) -> bool {
        !self.reviewed
    }
}

/// Decision from the content defense check.
///
/// Either the assertion passes all checks and should be indexed normally,
/// or it should be quarantined for manual review.
#[derive(Debug, Clone, PartialEq)]
pub enum QuarantineDecision {
    /// Assertion passed all checks; proceed with normal indexing.
    Pass,

    /// Assertion should be quarantined for the given reason.
    Quarantine(QuarantineReason),
}

impl QuarantineDecision {
    /// Check if this decision allows the assertion to pass.
    pub fn is_pass(&self) -> bool {
        matches!(self, Self::Pass)
    }

    /// Check if this decision quarantines the assertion.
    pub fn is_quarantine(&self) -> bool {
        matches!(self, Self::Quarantine(_))
    }

    /// Get the quarantine reason, if quarantined.
    pub fn reason(&self) -> Option<QuarantineReason> {
        match self {
            Self::Pass => None,
            Self::Quarantine(reason) => Some(*reason),
        }
    }
}

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

    #[test]
    fn test_content_quality_default() {
        let quality = ContentQuality::default();
        assert!((quality.score - 1.0).abs() < f32::EPSILON);
        assert!((quality.entropy - 3.0).abs() < f32::EPSILON);
        assert!(!quality.structured);
        assert!(!quality.duplicate);
    }

    #[test]
    fn test_content_quality_meets_threshold() {
        let mut quality = ContentQuality::new();

        quality.score = 0.5;
        assert!(quality.meets_threshold(0.4));
        assert!(quality.meets_threshold(0.5));
        assert!(!quality.meets_threshold(0.6));
    }

    #[test]
    fn test_quarantine_reason_serialization_roundtrip() {
        let reasons = [
            QuarantineReason::LowQuality,
            QuarantineReason::Duplicate,
            QuarantineReason::UntrustedHighConfidence,
            QuarantineReason::PatternMatch,
        ];

        for reason in reasons {
            let event =
                QuarantineEvent::new([0u8; 32], vec![1, 2, 3], reason, ContentQuality::new(), 1000);

            let bytes = serde::serialize(&event).expect("serialize");
            let restored: QuarantineEvent = serde::deserialize(&bytes).expect("deserialize");

            assert_eq!(restored.reason, reason);
        }
    }

    #[test]
    fn test_quarantine_event_lifecycle() {
        let mut event = QuarantineEvent::new(
            [1u8; 32],
            vec![1, 2, 3, 4],
            QuarantineReason::Duplicate,
            ContentQuality::new(),
            1000,
        );

        assert!(event.is_pending());
        assert!(!event.reviewed);
        assert!(event.approved.is_none());

        event.mark_reviewed(true);

        assert!(!event.is_pending());
        assert!(event.reviewed);
        assert_eq!(event.approved, Some(true));
    }

    #[test]
    fn test_quarantine_event_builder_pattern() {
        let event = QuarantineEvent::new(
            [1u8; 32],
            vec![1, 2, 3],
            QuarantineReason::Duplicate,
            ContentQuality::new(),
            1000,
        )
        .with_similar_to([2u8; 32])
        .with_agent_id([3u8; 32]);

        assert_eq!(event.similar_to, Some([2u8; 32]));
        assert_eq!(event.agent_id, Some([3u8; 32]));
    }

    #[test]
    fn test_quarantine_decision() {
        let pass = QuarantineDecision::Pass;
        assert!(pass.is_pass());
        assert!(!pass.is_quarantine());
        assert!(pass.reason().is_none());

        let quarantine = QuarantineDecision::Quarantine(QuarantineReason::LowQuality);
        assert!(!quarantine.is_pass());
        assert!(quarantine.is_quarantine());
        assert_eq!(quarantine.reason(), Some(QuarantineReason::LowQuality));
    }

    #[test]
    fn test_quarantine_reason_descriptions() {
        // Ensure all reasons have descriptions
        assert!(!QuarantineReason::LowQuality.description().is_empty());
        assert!(!QuarantineReason::Duplicate.description().is_empty());
        assert!(!QuarantineReason::UntrustedHighConfidence.description().is_empty());
        assert!(!QuarantineReason::PatternMatch.description().is_empty());
    }
}
