//! Analysis implementation for SkepticLens.
//!
//! Contains the helper methods for conflict analysis and the AnalysisLens trait
//! implementation.

use super::SkepticLens;
use crate::traits::AnalysisLens;
use async_trait::async_trait;
use std::collections::HashMap;
use stemedb_core::types::{
    AgentSummary, Assertion, ClaimSummary, ConflictAnalysis, Hash, ObjectValue, ResolutionStatus,
    SourceSummary,
};
use stemedb_storage::trust_rank_store::TrustRankStore;
use stemedb_storage::vote_store::VoteStore;
use tracing::{debug, instrument};

impl<V: VoteStore, T: TrustRankStore> SkepticLens<V, T> {
    /// Compute the content-addressed hash of an assertion.
    pub(crate) fn compute_assertion_hash(assertion: &Assertion) -> Hash {
        let bytes = match stemedb_core::serde::serialize(assertion) {
            Ok(b) => b,
            Err(_) => return [0u8; 32],
        };
        let hash_bytes = blake3::hash(&bytes);
        *hash_bytes.as_bytes()
    }

    /// Calculate normalized Shannon entropy for conflict score.
    ///
    /// # Arguments
    /// * `weights` - Vector of (ObjectValue, aggregate_weight) tuples
    ///
    /// # Returns
    /// Normalized entropy between 0.0 (unanimous) and 1.0 (maximum chaos)
    pub(crate) fn calculate_conflict_score(weights: &[(ObjectValue, f32)]) -> f32 {
        if weights.len() <= 1 {
            return 0.0; // Unanimous by definition
        }

        let total_weight: f32 = weights.iter().map(|(_, w)| w).sum();
        if total_weight <= 0.0 {
            return 0.0;
        }

        // Shannon entropy: H = -sum(p * log2(p))
        let entropy: f32 = weights
            .iter()
            .filter(|(_, w)| *w > 0.0)
            .map(|(_, w)| {
                let p = w / total_weight;
                -p * p.log2()
            })
            .sum();

        // Normalize by max possible entropy (log2(n) for n claims)
        let max_entropy = (weights.len() as f32).log2();
        if max_entropy <= 0.0 {
            return 0.0;
        }

        (entropy / max_entropy).min(1.0)
    }

    /// Get aggregate vote weight for an assertion.
    ///
    /// If the assertion has votes, returns the aggregate vote weight.
    /// If no votes exist, falls back to the assertion's own confidence score.
    async fn get_assertion_weight(&self, assertion: &Assertion) -> f32 {
        let hash = Self::compute_assertion_hash(assertion);
        match self.vote_store.get_aggregate_weight(&hash, &assertion.subject).await {
            Ok(weight) if weight > 0.0 => weight,
            Ok(_) => {
                // No votes exist, fall back to assertion confidence
                debug!(
                    assertion_hash = %hex::encode(hash),
                    confidence = assertion.confidence,
                    "No votes found, using assertion confidence"
                );
                assertion.confidence
            }
            Err(e) => {
                debug!(
                    assertion_hash = %hex::encode(hash),
                    error = %e,
                    "Failed to get vote weight, using assertion confidence"
                );
                assertion.confidence
            }
        }
    }

    /// Build a ClaimSummary for a group of assertions with the same object value.
    async fn build_claim_summary(
        &self,
        value: ObjectValue,
        weight_share: f32,
        assertions: &[&Assertion],
    ) -> ClaimSummary {
        // Find the representative assertion (highest confidence)
        let representative = assertions
            .iter()
            .max_by(|a, b| {
                a.confidence.partial_cmp(&b.confidence).unwrap_or(std::cmp::Ordering::Equal)
            })
            .copied();

        let representative_hash =
            representative.map(Self::compute_assertion_hash).unwrap_or([0u8; 32]);

        // Build source summary from representative
        let source = representative
            .map(|a| SourceSummary { source_hash: a.source_hash, visual_hash: a.visual_hash })
            .unwrap_or(SourceSummary { source_hash: [0u8; 32], visual_hash: None });

        // Collect unique agents from all assertions in this group
        let mut agent_ids: Vec<[u8; 32]> = Vec::new();
        for assertion in assertions {
            for sig in &assertion.signatures {
                if !agent_ids.contains(&sig.agent_id) {
                    agent_ids.push(sig.agent_id);
                }
            }
        }

        // Build AgentSummary with trust scores
        let mut supporting_agents = Vec::with_capacity(agent_ids.len());
        for agent_id in agent_ids {
            let trust_score = match self.trust_store.get_trust_rank(&agent_id).await {
                Ok(trust_rank) => trust_rank.score,
                Err(_) => 0.5, // Default for unknown agents
            };
            supporting_agents.push(AgentSummary { agent_id, trust_score });
        }

        ClaimSummary {
            value,
            weight_share,
            assertion_count: assertions.len() as u32,
            representative_hash,
            source,
            supporting_agents,
        }
    }
}

#[async_trait]
impl<V: VoteStore + 'static, T: TrustRankStore + 'static> AnalysisLens for SkepticLens<V, T> {
    #[instrument(skip(self, candidates), fields(candidates_count = candidates.len()))]
    async fn analyze(&self, candidates: &[Assertion]) -> ConflictAnalysis {
        if candidates.is_empty() {
            return ConflictAnalysis::empty();
        }

        // Step 1: Group assertions by object value
        let mut groups: HashMap<String, Vec<&Assertion>> = HashMap::new();
        for assertion in candidates {
            let key = format!("{:?}", assertion.object);
            groups.entry(key).or_default().push(assertion);
        }

        // Step 2: Calculate aggregate weight for each group
        let mut group_weights: Vec<(ObjectValue, f32, Vec<&Assertion>)> =
            Vec::with_capacity(groups.len());

        for assertions in groups.values() {
            let mut total_weight = 0.0_f32;
            for assertion in assertions {
                total_weight += self.get_assertion_weight(assertion).await;
            }

            // Use the object value from the first assertion in the group
            if let Some(first) = assertions.first() {
                group_weights.push((first.object.clone(), total_weight, assertions.clone()));
            }
        }

        // Step 3: Calculate conflict score
        let weight_pairs: Vec<(ObjectValue, f32)> =
            group_weights.iter().map(|(v, w, _)| (v.clone(), *w)).collect();
        let conflict_score = Self::calculate_conflict_score(&weight_pairs);
        let status = ResolutionStatus::from_conflict_score(conflict_score, groups.len());

        debug!(
            conflict_score,
            claim_count = groups.len(),
            status = ?status,
            "Calculated conflict analysis"
        );

        // Step 4: Calculate total weight for normalization
        let total_weight: f32 = group_weights.iter().map(|(_, w, _)| w).sum();

        // Step 5: Build ClaimSummaries
        let mut claims = Vec::with_capacity(group_weights.len());
        for (value, weight, assertions) in group_weights {
            let weight_share = if total_weight > 0.0 { weight / total_weight } else { 0.0 };
            let claim = self.build_claim_summary(value, weight_share, &assertions).await;
            claims.push(claim);
        }

        // Step 6: Sort claims by weight_share descending
        claims.sort_by(|a, b| {
            b.weight_share.partial_cmp(&a.weight_share).unwrap_or(std::cmp::Ordering::Equal)
        });

        ConflictAnalysis { status, conflict_score, claims, candidates_count: candidates.len() }
    }

    fn name(&self) -> &'static str {
        "Skeptic"
    }
}
