//! Content quality scoring for spam detection.
//!
//! This module provides quality scoring for assertions based on:
//! - Shannon entropy (low entropy = suspicious random noise)
//! - Length checks (too short = likely spam)
//! - Structured data detection (JSON, numbers, URLs get bonuses)
//! - Suspicious patterns (untrusted + high confidence)

use stemedb_core::types::{Assertion, ContentQuality, ObjectValue, TrustTier};

/// Convert an ObjectValue to a string for analysis.
fn object_value_to_string(value: &ObjectValue) -> String {
    match value {
        ObjectValue::Text(s) => s.clone(),
        ObjectValue::Number(n) => n.to_string(),
        ObjectValue::Boolean(b) => b.to_string(),
        ObjectValue::Reference(r) => r.clone(),
    }
}

/// Configuration for the quality scorer.
#[derive(Debug, Clone)]
pub struct QualityScoringConfig {
    /// Minimum subject length in characters.
    pub min_subject_len: usize,

    /// Minimum predicate length in characters.
    pub min_predicate_len: usize,

    /// Entropy threshold in bits/char. Below this is suspicious.
    pub entropy_threshold: f32,

    /// Quality score threshold. Below this triggers quarantine.
    pub quality_threshold: f32,

    /// Confidence threshold for untrusted agents. Above this is suspicious.
    pub untrusted_confidence_threshold: f32,
}

impl Default for QualityScoringConfig {
    fn default() -> Self {
        Self {
            min_subject_len: 3,
            min_predicate_len: 3,
            entropy_threshold: 1.5,
            quality_threshold: 0.4,
            untrusted_confidence_threshold: 0.8,
        }
    }
}

/// Quality scorer for assertion content.
///
/// Computes various quality metrics to detect spam and low-quality content.
pub struct ContentQualityScorer {
    config: QualityScoringConfig,
}

impl ContentQualityScorer {
    /// Create a new quality scorer with the given configuration.
    pub fn new(config: QualityScoringConfig) -> Self {
        Self { config }
    }

    /// Create a new quality scorer with default configuration.
    pub fn with_defaults() -> Self {
        Self::new(QualityScoringConfig::default())
    }

    /// Get the configuration.
    pub fn config(&self) -> &QualityScoringConfig {
        &self.config
    }

    /// Score an assertion's quality.
    ///
    /// Returns a [`ContentQuality`] with metrics that can be used to decide
    /// whether to quarantine the assertion.
    pub fn score(&self, assertion: &Assertion, trust_tier: TrustTier) -> ContentQuality {
        let subject = &assertion.subject;
        let predicate = &assertion.predicate;

        // Compute entropy
        let text = format!("{}:{}", subject, predicate);
        let entropy = self.compute_entropy(&text);

        // Check if structured data
        let structured = self.is_structured(assertion);

        // Start with base score
        let mut score: f32 = 1.0;

        // Length penalty
        if subject.len() < self.config.min_subject_len {
            score -= 0.3;
        }
        if predicate.len() < self.config.min_predicate_len {
            score -= 0.3;
        }

        // Entropy penalty
        if entropy < self.config.entropy_threshold {
            score -= 0.3;
        }

        // Structured data bonus
        if structured {
            score += 0.1;
        }

        // Suspicious pattern: untrusted + high confidence
        if matches!(trust_tier, TrustTier::Untrusted | TrustTier::Limited)
            && assertion.confidence > self.config.untrusted_confidence_threshold
        {
            score -= 0.5;
        }

        // Clamp to [0.0, 1.0]
        score = score.clamp(0.0, 1.0);

        ContentQuality { score, entropy, structured, duplicate: false }
    }

    /// Check if an assertion's object looks like structured data.
    ///
    /// Structured data (JSON, numbers, URLs) is more likely to be legitimate.
    fn is_structured(&self, assertion: &Assertion) -> bool {
        let object_str = object_value_to_string(&assertion.object);

        // Check for JSON-like patterns
        if (object_str.starts_with('{') && object_str.ends_with('}'))
            || (object_str.starts_with('[') && object_str.ends_with(']'))
        {
            return true;
        }

        // Check for URL-like patterns
        if object_str.starts_with("http://") || object_str.starts_with("https://") {
            return true;
        }

        // Check for pure numeric
        if object_str.parse::<f64>().is_ok() {
            return true;
        }

        // Check for date-like patterns (YYYY-MM-DD)
        if object_str.len() == 10
            && object_str.chars().nth(4) == Some('-')
            && object_str.chars().nth(7) == Some('-')
        {
            return true;
        }

        false
    }

    /// Compute Shannon entropy of a string in bits per character.
    ///
    /// Low entropy (< 1.5 bits/char) suggests:
    /// - Random keyboard mashing
    /// - Repetitive spam
    /// - Single-character padding
    fn compute_entropy(&self, text: &str) -> f32 {
        if text.is_empty() {
            return 0.0;
        }

        // Count character frequencies
        let mut freq = [0u32; 256];
        let mut total = 0u32;

        for byte in text.bytes() {
            freq[byte as usize] += 1;
            total += 1;
        }

        // Compute Shannon entropy
        let mut entropy: f32 = 0.0;
        for count in freq.iter() {
            if *count > 0 {
                let p = *count as f32 / total as f32;
                entropy -= p * p.log2();
            }
        }

        entropy
    }

    /// Check if content meets the quality threshold.
    pub fn meets_threshold(&self, quality: &ContentQuality) -> bool {
        quality.score >= self.config.quality_threshold
    }

    /// Check for suspicious patterns (untrusted + high confidence).
    pub fn is_suspicious_pattern(&self, trust_tier: TrustTier, confidence: f32) -> bool {
        matches!(trust_tier, TrustTier::Untrusted | TrustTier::Limited)
            && confidence > self.config.untrusted_confidence_threshold
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use stemedb_core::testing::AssertionBuilder;
    use stemedb_core::types::{LifecycleStage, ObjectValue};

    fn create_test_assertion(subject: &str, predicate: &str, object: ObjectValue) -> Assertion {
        AssertionBuilder::new()
            .subject(subject)
            .predicate(predicate)
            .object(object)
            .confidence(0.5)
            .lifecycle(LifecycleStage::Proposed)
            .build()
    }

    #[test]
    fn test_entropy_normal_text() {
        let scorer = ContentQualityScorer::with_defaults();

        // Normal English text has ~4 bits/char entropy
        let entropy = scorer.compute_entropy("The quick brown fox jumps over the lazy dog");
        assert!(entropy > 3.0, "Expected high entropy for natural text, got {}", entropy);
    }

    #[test]
    fn test_entropy_repetitive() {
        let scorer = ContentQualityScorer::with_defaults();

        // Repetitive text has low entropy
        let entropy = scorer.compute_entropy("aaaaaaaaaa");
        assert!(entropy < 0.5, "Expected low entropy for repetitive text, got {}", entropy);
    }

    #[test]
    fn test_entropy_empty() {
        let scorer = ContentQualityScorer::with_defaults();

        let entropy = scorer.compute_entropy("");
        assert!((entropy - 0.0).abs() < f32::EPSILON, "Empty string should have 0 entropy");
    }

    #[test]
    fn test_score_normal_assertion() {
        let scorer = ContentQualityScorer::with_defaults();

        let assertion =
            create_test_assertion("Tesla_Inc", "has_revenue", ObjectValue::Number(95000000000.0));

        let quality = scorer.score(&assertion, TrustTier::Verified);

        assert!(quality.score >= 0.8, "Normal assertion should have high quality score");
        assert!(quality.entropy > 2.0, "Normal text should have reasonable entropy");
        assert!(quality.structured, "Numeric object should be detected as structured");
    }

    #[test]
    fn test_score_short_subject() {
        let scorer = ContentQualityScorer::with_defaults();

        // Both subject AND predicate are short (below 3 chars each)
        let assertion = create_test_assertion("AB", "xy", ObjectValue::Number(100.0));

        let quality = scorer.score(&assertion, TrustTier::Verified);

        // Short subject and predicate get penalties (-0.3 each)
        assert!(
            quality.score < 0.5,
            "Short subject/predicate should lower quality score, got {}",
            quality.score
        );
    }

    #[test]
    fn test_score_untrusted_high_confidence() {
        let scorer = ContentQualityScorer::with_defaults();

        let mut assertion =
            create_test_assertion("Tesla_Inc", "has_revenue", ObjectValue::Number(95000000000.0));
        assertion.confidence = 0.95;

        let quality = scorer.score(&assertion, TrustTier::Untrusted);

        // Untrusted + high confidence is suspicious
        // Base score 1.0, structured bonus +0.1, untrusted penalty -0.5 = 0.6
        assert!(
            quality.score <= 0.6,
            "Untrusted + high confidence should lower quality score significantly, got {}",
            quality.score
        );
    }

    #[test]
    fn test_score_trusted_high_confidence() {
        let scorer = ContentQualityScorer::with_defaults();

        let mut assertion =
            create_test_assertion("Tesla_Inc", "has_revenue", ObjectValue::Number(95000000000.0));
        assertion.confidence = 0.95;

        let quality = scorer.score(&assertion, TrustTier::Authority);

        // Authority with high confidence is fine
        assert!(quality.score >= 0.8, "Authority + high confidence should be trusted");
    }

    #[test]
    fn test_is_structured_json() {
        let scorer = ContentQualityScorer::with_defaults();

        let assertion = create_test_assertion(
            "Tesla",
            "data",
            ObjectValue::Text(r#"{"revenue": 95000000000}"#.to_string()),
        );

        assert!(scorer.is_structured(&assertion), "JSON-like object should be detected");
    }

    #[test]
    fn test_is_structured_url() {
        let scorer = ContentQualityScorer::with_defaults();

        let assertion = create_test_assertion(
            "Tesla",
            "website",
            ObjectValue::Text("https://www.tesla.com".to_string()),
        );

        assert!(scorer.is_structured(&assertion), "URL should be detected as structured");
    }

    #[test]
    fn test_is_structured_number() {
        let scorer = ContentQualityScorer::with_defaults();

        let assertion =
            create_test_assertion("Tesla", "revenue", ObjectValue::Number(95000000000.0));

        assert!(scorer.is_structured(&assertion), "Number should be detected as structured");
    }

    #[test]
    fn test_is_structured_date() {
        let scorer = ContentQualityScorer::with_defaults();

        let assertion =
            create_test_assertion("Tesla", "founded", ObjectValue::Text("2003-07-01".to_string()));

        assert!(scorer.is_structured(&assertion), "Date should be detected as structured");
    }

    #[test]
    fn test_meets_threshold() {
        let scorer = ContentQualityScorer::with_defaults();

        let high_quality =
            ContentQuality { score: 0.8, entropy: 3.0, structured: true, duplicate: false };
        assert!(scorer.meets_threshold(&high_quality));

        let low_quality =
            ContentQuality { score: 0.2, entropy: 1.0, structured: false, duplicate: false };
        assert!(!scorer.meets_threshold(&low_quality));

        let borderline =
            ContentQuality { score: 0.4, entropy: 2.0, structured: false, duplicate: false };
        assert!(scorer.meets_threshold(&borderline)); // Exactly at threshold
    }

    #[test]
    fn test_is_suspicious_pattern() {
        let scorer = ContentQualityScorer::with_defaults();

        assert!(scorer.is_suspicious_pattern(TrustTier::Untrusted, 0.9));
        assert!(scorer.is_suspicious_pattern(TrustTier::Limited, 0.85));
        assert!(!scorer.is_suspicious_pattern(TrustTier::Verified, 0.95));
        assert!(!scorer.is_suspicious_pattern(TrustTier::Untrusted, 0.5));
    }
}
