//! DomainTrustStore data models for domain-specific trust tracking.
//!
//! This module defines the core data structures for per-domain expertise:
//! - `DomainTrust`: An agent's trust score within a specific domain
//! - Domain extraction from predicates

/// Default domain trust score for new agent-domain pairs.
pub const DEFAULT_DOMAIN_SCORE: f32 = 0.5;

/// Domain trust for an agent within a specific domain.
///
/// Tracks an agent's expertise and accuracy within a domain (e.g., "medicine", "finance").
/// This allows fine-grained trust: an agent can be highly trusted in medicine
/// but untrusted in finance.
///
/// # Invariants
///
/// - `score` is in range [0.0, 1.0]
/// - `assertions_count >= accuracy_count`
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct DomainTrust {
    /// Agent's Ed25519 public key.
    pub agent_id: [u8; 32],
    /// Domain name (e.g., "medicine", "finance", "general").
    pub domain: String,
    /// Domain-specific trust score (0.0 to 1.0).
    pub score: f32,
    /// Total assertions made in this domain.
    pub assertions_count: u64,
    /// Assertions deemed accurate in this domain.
    pub accuracy_count: u64,
    /// Unix timestamp of last update.
    pub last_updated: u64,
}

impl DomainTrust {
    /// Create a new domain trust entry with default score.
    pub fn new(agent_id: [u8; 32], domain: String, timestamp: u64) -> Self {
        Self {
            agent_id,
            domain,
            score: DEFAULT_DOMAIN_SCORE,
            assertions_count: 0,
            accuracy_count: 0,
            last_updated: timestamp,
        }
    }

    /// Record an outcome for this domain.
    ///
    /// Updates the accuracy tracking and adjusts the score based on outcome.
    ///
    /// # Arguments
    /// * `was_accurate` - Whether the assertion was correct
    /// * `timestamp` - Unix timestamp of this outcome
    ///
    /// # Returns
    /// The new score after recording the outcome
    pub fn record_outcome(&mut self, was_accurate: bool, timestamp: u64) -> f32 {
        self.assertions_count = self.assertions_count.saturating_add(1);
        if was_accurate {
            self.accuracy_count = self.accuracy_count.saturating_add(1);
        }
        self.last_updated = timestamp;

        // Adjust score based on accuracy
        // Accurate: +0.03, Inaccurate: -0.05 (penalty is higher)
        let delta = if was_accurate { 0.03 } else { -0.05 };
        self.score = (self.score + delta).clamp(0.0, 1.0);

        self.score
    }

    /// Calculate the agent's accuracy rate in this domain.
    ///
    /// Returns 0.0 if no assertions have been made.
    pub fn accuracy_rate(&self) -> f32 {
        if self.assertions_count == 0 {
            return 0.0;
        }
        self.accuracy_count as f32 / self.assertions_count as f32
    }
}

/// Predicate-to-domain mapping rules.
///
/// This is a curated list of predicate patterns and their domains.
/// Used by `extract_domain()` to categorize assertions.
static DOMAIN_MAPPINGS: &[(&str, &str)] = &[
    // Medicine / Health
    ("treats", "medicine"),
    ("treats_condition", "medicine"),
    ("has_side_effect", "medicine"),
    ("contraindicated", "medicine"),
    ("dosage", "medicine"),
    ("symptoms", "medicine"),
    ("diagnoses", "medicine"),
    ("prescribed_for", "medicine"),
    ("drug_interaction", "medicine"),
    ("clinical_trial", "medicine"),
    // Finance
    ("has_revenue", "finance"),
    ("market_cap", "finance"),
    ("stock_price", "finance"),
    ("earnings", "finance"),
    ("profit_margin", "finance"),
    ("debt_ratio", "finance"),
    ("dividend_yield", "finance"),
    ("pe_ratio", "finance"),
    // Technology
    ("implements", "technology"),
    ("uses_framework", "technology"),
    ("depends_on", "technology"),
    ("version", "technology"),
    ("api_endpoint", "technology"),
    ("deprecates", "technology"),
    // Science
    ("atomic_weight", "science"),
    ("chemical_formula", "science"),
    ("discovered_by", "science"),
    ("speed_of", "science"),
    ("temperature", "science"),
    ("pressure", "science"),
    // Geography
    ("located_in", "geography"),
    ("capital_of", "geography"),
    ("population", "geography"),
    ("coordinates", "geography"),
    ("borders", "geography"),
    ("area", "geography"),
    // Legal
    ("enacted_by", "legal"),
    ("effective_date", "legal"),
    ("jurisdiction", "legal"),
    ("supersedes", "legal"),
    ("penalty", "legal"),
    // General (catch-all patterns)
    ("has_name", "general"),
    ("has_type", "general"),
    ("is_a", "general"),
    ("part_of", "general"),
];

/// Extract the domain from a predicate string.
///
/// Uses a curated mapping of predicate patterns to domains.
/// Falls back to "general" if no specific mapping is found.
///
/// # Examples
///
/// ```ignore
/// assert_eq!(extract_domain("treats_condition"), "medicine");
/// assert_eq!(extract_domain("has_revenue"), "finance");
/// assert_eq!(extract_domain("unknown_predicate"), "general");
/// ```
pub fn extract_domain(predicate: &str) -> String {
    let predicate_lower = predicate.to_lowercase();

    // Check exact matches first
    for (pattern, domain) in DOMAIN_MAPPINGS {
        if predicate_lower == *pattern {
            return (*domain).to_string();
        }
    }

    // Check prefix matches (e.g., "treats_xyz" → "medicine")
    for (pattern, domain) in DOMAIN_MAPPINGS {
        if predicate_lower.starts_with(pattern) {
            return (*domain).to_string();
        }
    }

    // Check contains matches (e.g., "xyz_treats_abc" → "medicine")
    for (pattern, domain) in DOMAIN_MAPPINGS {
        if predicate_lower.contains(pattern) {
            return (*domain).to_string();
        }
    }

    // Default to general domain
    "general".to_string()
}

/// Calculate the domain factor for weighting assertions.
///
/// Returns a multiplier based on the agent's domain trust score.
/// This is used to scale the global EigenTrust score by domain expertise.
///
/// # Formula
///
/// `factor = 0.5 + (domain_score * 0.5)`
///
/// - Score 0.0 → Factor 0.5 (halved weight)
/// - Score 0.5 → Factor 0.75 (default, slight reduction)
/// - Score 1.0 → Factor 1.0 (full weight)
pub fn domain_factor(domain_score: f32) -> f32 {
    0.5 + (domain_score.clamp(0.0, 1.0) * 0.5)
}

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

    #[test]
    fn test_domain_trust_new() {
        let dt = DomainTrust::new([1u8; 32], "medicine".to_string(), 1000);
        assert!((dt.score - 0.5).abs() < f32::EPSILON);
        assert_eq!(dt.assertions_count, 0);
        assert_eq!(dt.accuracy_count, 0);
    }

    #[test]
    fn test_domain_trust_record_outcome_accurate() {
        let mut dt = DomainTrust::new([1u8; 32], "medicine".to_string(), 1000);
        let new_score = dt.record_outcome(true, 2000);

        assert!((new_score - 0.53).abs() < 0.01);
        assert_eq!(dt.assertions_count, 1);
        assert_eq!(dt.accuracy_count, 1);
        assert_eq!(dt.last_updated, 2000);
    }

    #[test]
    fn test_domain_trust_record_outcome_inaccurate() {
        let mut dt = DomainTrust::new([1u8; 32], "medicine".to_string(), 1000);
        let new_score = dt.record_outcome(false, 2000);

        assert!((new_score - 0.45).abs() < 0.01);
        assert_eq!(dt.assertions_count, 1);
        assert_eq!(dt.accuracy_count, 0);
    }

    #[test]
    fn test_domain_trust_accuracy_rate() {
        let mut dt = DomainTrust::new([1u8; 32], "medicine".to_string(), 1000);

        // No assertions yet
        assert!((dt.accuracy_rate() - 0.0).abs() < f32::EPSILON);

        // 3 accurate, 1 inaccurate = 75% accuracy
        dt.record_outcome(true, 1001);
        dt.record_outcome(true, 1002);
        dt.record_outcome(true, 1003);
        dt.record_outcome(false, 1004);

        assert!((dt.accuracy_rate() - 0.75).abs() < 0.01);
    }

    #[test]
    fn test_extract_domain_exact_match() {
        assert_eq!(extract_domain("treats_condition"), "medicine");
        assert_eq!(extract_domain("has_revenue"), "finance");
        assert_eq!(extract_domain("implements"), "technology");
        assert_eq!(extract_domain("located_in"), "geography");
    }

    #[test]
    fn test_extract_domain_prefix_match() {
        assert_eq!(extract_domain("treats_xyz"), "medicine");
        assert_eq!(extract_domain("stock_price_daily"), "finance");
    }

    #[test]
    fn test_extract_domain_case_insensitive() {
        assert_eq!(extract_domain("TREATS_CONDITION"), "medicine");
        assert_eq!(extract_domain("Has_Revenue"), "finance");
    }

    #[test]
    fn test_extract_domain_default_general() {
        assert_eq!(extract_domain("unknown_predicate"), "general");
        assert_eq!(extract_domain("foo_bar_baz"), "general");
    }

    #[test]
    fn test_domain_factor() {
        assert!((domain_factor(0.0) - 0.5).abs() < f32::EPSILON);
        assert!((domain_factor(0.5) - 0.75).abs() < f32::EPSILON);
        assert!((domain_factor(1.0) - 1.0).abs() < f32::EPSILON);

        // Clamping
        assert!((domain_factor(-1.0) - 0.5).abs() < f32::EPSILON);
        assert!((domain_factor(2.0) - 1.0).abs() < f32::EPSILON);
    }
}
