//! DomainTrustStore implementation backed by a generic KVStore.
//!
//! This module provides the concrete implementation of DomainTrustStore operations,
//! including CRUD operations and effective trust calculation.

use crate::error::Result;
use crate::key_codec;
use crate::traits::KVStore;
use async_trait::async_trait;
use tracing::{debug, instrument};

use super::model::{domain_factor, extract_domain, DomainTrust};
use super::DomainTrustStore;

/// DomainTrustStore implementation backed by a generic KVStore.
///
/// This implementation stores DomainTrust data at `\x00DT:{agent_hex}:{domain}`
/// and provides all operations for domain-specific trust management.
pub struct GenericDomainTrustStore<S> {
    store: S,
}

impl<S: KVStore> GenericDomainTrustStore<S> {
    /// Create a new DomainTrustStore backed by the given KVStore.
    pub fn new(store: S) -> Self {
        Self { store }
    }

    /// Serialize a DomainTrust using the canonical serde helpers.
    fn serialize_domain_trust(dt: &DomainTrust) -> Result<Vec<u8>> {
        crate::serde_helpers::serialize(dt)
    }

    /// Deserialize a DomainTrust using the canonical serde helpers.
    fn deserialize_domain_trust(data: &[u8]) -> Result<DomainTrust> {
        crate::serde_helpers::deserialize(data)
    }
}

#[async_trait]
impl<S: KVStore + 'static> DomainTrustStore for GenericDomainTrustStore<S> {
    #[instrument(skip(self), fields(agent = %hex::encode(agent), domain))]
    async fn get_domain_trust(&self, agent: &[u8; 32], domain: &str) -> Result<DomainTrust> {
        let agent_hex = hex::encode(agent);
        let key = key_codec::domain_trust_key(&agent_hex, domain);

        match self.store.get(&key).await? {
            Some(data) => {
                let dt = Self::deserialize_domain_trust(&data)?;
                debug!(score = dt.score, assertions = dt.assertions_count, "Retrieved DomainTrust");
                Ok(dt)
            }
            None => {
                // New agent-domain pair, return default
                let now = std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .map(|d| d.as_secs())
                    .unwrap_or_else(|e| {
                        tracing::warn!(error = %e, "System clock error, using epoch timestamp");
                        0
                    });
                let dt = DomainTrust::new(*agent, domain.to_string(), now);
                debug!(score = dt.score, "Created default DomainTrust for new agent-domain pair");
                Ok(dt)
            }
        }
    }

    #[instrument(skip(self), fields(agent = %hex::encode(agent)))]
    async fn get_all_domains_for_agent(&self, agent: &[u8; 32]) -> Result<Vec<DomainTrust>> {
        let agent_hex = hex::encode(agent);
        let prefix = key_codec::domain_trust_agent_prefix(&agent_hex);
        let entries = self.store.scan_prefix(&prefix).await?;

        let mut domains = Vec::with_capacity(entries.len());
        for (_, data) in entries {
            let dt = Self::deserialize_domain_trust(&data)?;
            domains.push(dt);
        }

        debug!(count = domains.len(), "Retrieved all domains for agent");
        Ok(domains)
    }

    #[instrument(skip(self), fields(
        agent = %hex::encode(agent),
        predicate,
        was_accurate
    ))]
    async fn record_domain_outcome(
        &self,
        agent: &[u8; 32],
        predicate: &str,
        was_accurate: bool,
        timestamp: u64,
    ) -> Result<f32> {
        // Extract domain from predicate
        let domain = extract_domain(predicate);
        debug!(domain = %domain, "Extracted domain from predicate");

        // Get or create domain trust
        let mut dt = self.get_domain_trust(agent, &domain).await?;

        // Record the outcome
        let new_score = dt.record_outcome(was_accurate, timestamp);

        // Store updated domain trust
        let agent_hex = hex::encode(agent);
        let key = key_codec::domain_trust_key(&agent_hex, &domain);
        let serialized = Self::serialize_domain_trust(&dt)?;
        self.store.put(&key, &serialized).await?;

        debug!(
            new_score,
            accuracy_rate = dt.accuracy_rate(),
            "Recorded domain outcome and updated DomainTrust"
        );
        Ok(new_score)
    }

    #[instrument(skip(self, dt), fields(
        agent = %hex::encode(dt.agent_id),
        domain = %dt.domain
    ))]
    async fn put_domain_trust(&self, dt: &DomainTrust) -> Result<()> {
        let agent_hex = hex::encode(dt.agent_id);
        let key = key_codec::domain_trust_key(&agent_hex, &dt.domain);
        let serialized = Self::serialize_domain_trust(dt)?;
        self.store.put(&key, &serialized).await?;

        debug!(score = dt.score, "Stored DomainTrust");
        Ok(())
    }

    #[instrument(skip(self), fields(
        agent = %hex::encode(agent),
        predicate,
        eigentrust_score
    ))]
    async fn get_effective_trust(
        &self,
        agent: &[u8; 32],
        predicate: &str,
        eigentrust_score: f32,
    ) -> Result<f32> {
        // Extract domain from predicate
        let domain = extract_domain(predicate);

        // Get domain trust (returns default 0.5 if not found)
        let dt = self.get_domain_trust(agent, &domain).await?;

        // Calculate effective trust
        let factor = domain_factor(dt.score);
        let effective = eigentrust_score * factor;

        debug!(
            domain = %domain,
            domain_score = dt.score,
            factor,
            effective,
            "Calculated effective trust"
        );

        Ok(effective)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::HybridStore;
    use std::sync::Arc;

    fn agent(id: u8) -> [u8; 32] {
        let mut arr = [0u8; 32];
        arr[0] = id;
        arr
    }

    #[tokio::test]
    async fn test_get_domain_trust_default() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Non-existent should return default
        let dt = domain_store.get_domain_trust(&agent(1), "medicine").await.expect("get");

        assert_eq!(dt.agent_id, agent(1));
        assert_eq!(dt.domain, "medicine");
        assert!((dt.score - 0.5).abs() < f32::EPSILON); // Default score
        assert_eq!(dt.assertions_count, 0);
    }

    #[tokio::test]
    async fn test_put_and_get_domain_trust() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        let mut dt = DomainTrust::new(agent(1), "medicine".to_string(), 1000);
        dt.score = 0.8;
        dt.assertions_count = 10;
        dt.accuracy_count = 8;

        domain_store.put_domain_trust(&dt).await.expect("put");

        let retrieved = domain_store.get_domain_trust(&agent(1), "medicine").await.expect("get");
        assert!((retrieved.score - 0.8).abs() < f32::EPSILON);
        assert_eq!(retrieved.assertions_count, 10);
        assert_eq!(retrieved.accuracy_count, 8);
    }

    #[tokio::test]
    async fn test_record_domain_outcome_accurate() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Record accurate outcome for medicine domain
        let new_score = domain_store
            .record_domain_outcome(&agent(1), "treats_condition", true, 1000)
            .await
            .expect("record");

        // Score should increase from 0.5 → 0.53
        assert!((new_score - 0.53).abs() < 0.01);

        // Check the domain was extracted correctly
        let dt = domain_store.get_domain_trust(&agent(1), "medicine").await.expect("get");
        assert_eq!(dt.assertions_count, 1);
        assert_eq!(dt.accuracy_count, 1);
    }

    #[tokio::test]
    async fn test_record_domain_outcome_inaccurate() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Record inaccurate outcome
        let new_score = domain_store
            .record_domain_outcome(&agent(1), "has_revenue", false, 1000)
            .await
            .expect("record");

        // Score should decrease from 0.5 → 0.45
        assert!((new_score - 0.45).abs() < 0.01);

        // Check the domain was extracted correctly (finance)
        let dt = domain_store.get_domain_trust(&agent(1), "finance").await.expect("get");
        assert_eq!(dt.assertions_count, 1);
        assert_eq!(dt.accuracy_count, 0);
    }

    #[tokio::test]
    async fn test_get_all_domains_for_agent() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Agent 1 has activity in multiple domains
        domain_store
            .record_domain_outcome(&agent(1), "treats_condition", true, 1000)
            .await
            .expect("record");
        domain_store
            .record_domain_outcome(&agent(1), "has_revenue", true, 1001)
            .await
            .expect("record");
        domain_store
            .record_domain_outcome(&agent(1), "located_in", true, 1002)
            .await
            .expect("record");

        let domains = domain_store.get_all_domains_for_agent(&agent(1)).await.expect("get");
        assert_eq!(domains.len(), 3);

        // Check domains are correct
        let domain_names: Vec<&str> = domains.iter().map(|dt| dt.domain.as_str()).collect();
        assert!(domain_names.contains(&"medicine"));
        assert!(domain_names.contains(&"finance"));
        assert!(domain_names.contains(&"geography"));
    }

    #[tokio::test]
    async fn test_get_effective_trust() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Set up: agent has 0.8 domain score in medicine
        let mut dt = DomainTrust::new(agent(1), "medicine".to_string(), 1000);
        dt.score = 0.8;
        domain_store.put_domain_trust(&dt).await.expect("put");

        // Effective trust with 0.6 eigentrust
        // factor = 0.5 + (0.8 * 0.5) = 0.9
        // effective = 0.6 * 0.9 = 0.54
        let effective = domain_store
            .get_effective_trust(&agent(1), "treats_condition", 0.6)
            .await
            .expect("get");

        assert!((effective - 0.54).abs() < 0.01);
    }

    #[tokio::test]
    async fn test_get_effective_trust_default_domain() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // No domain trust set (default 0.5)
        // factor = 0.5 + (0.5 * 0.5) = 0.75
        // effective = 0.6 * 0.75 = 0.45
        let effective = domain_store
            .get_effective_trust(&agent(1), "treats_condition", 0.6)
            .await
            .expect("get");

        assert!((effective - 0.45).abs() < 0.01);
    }

    #[tokio::test]
    async fn test_domain_expertise_affects_resolution() {
        // Scenario: Two agents with same eigentrust, different domain expertise
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Agent 1: Expert in medicine (score 0.9)
        let mut dt1 = DomainTrust::new(agent(1), "medicine".to_string(), 1000);
        dt1.score = 0.9;
        domain_store.put_domain_trust(&dt1).await.expect("put");

        // Agent 2: Novice in medicine (score 0.3)
        let mut dt2 = DomainTrust::new(agent(2), "medicine".to_string(), 1000);
        dt2.score = 0.3;
        domain_store.put_domain_trust(&dt2).await.expect("put");

        // Both have same global eigentrust (0.7)
        let effective1 = domain_store
            .get_effective_trust(&agent(1), "treats_condition", 0.7)
            .await
            .expect("get");
        let effective2 = domain_store
            .get_effective_trust(&agent(2), "treats_condition", 0.7)
            .await
            .expect("get");

        // Agent 1 (expert) should have significantly higher effective trust
        assert!(effective1 > effective2 * 1.3, "Expert: {}, Novice: {}", effective1, effective2);
    }

    #[tokio::test]
    async fn test_domain_isolation() {
        // Scenario: Agent is expert in medicine but not in finance
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let domain_store = GenericDomainTrustStore::new(store);

        // Agent is expert in medicine
        let mut dt = DomainTrust::new(agent(1), "medicine".to_string(), 1000);
        dt.score = 0.95;
        domain_store.put_domain_trust(&dt).await.expect("put");

        // Agent has poor track record in finance
        let mut dt2 = DomainTrust::new(agent(1), "finance".to_string(), 1000);
        dt2.score = 0.2;
        domain_store.put_domain_trust(&dt2).await.expect("put");

        // Effective trust in medicine is high
        let effective_med = domain_store
            .get_effective_trust(&agent(1), "treats_condition", 0.8)
            .await
            .expect("get");

        // Effective trust in finance is low
        let effective_fin =
            domain_store.get_effective_trust(&agent(1), "has_revenue", 0.8).await.expect("get");

        assert!(effective_med > 0.7, "Medicine effective trust: {}", effective_med);
        assert!(effective_fin < 0.5, "Finance effective trust: {}", effective_fin);
    }
}
