//! Consensus Lens: Most common value wins.
//!
//! This lens selects assertions based on how many agents agree on the same value.
//! In Phase 2, this counts identical object values across assertions.
//! In Phase 4, this will integrate with the Ballot Box vote counts.

use crate::traits::{compute_conflict_score, Lens, Resolution};
use std::collections::HashMap;
use stemedb_core::types::Assertion;
use tracing::instrument;

/// Consensus Lens: Returns the assertion representing the most common value.
///
/// # Current Implementation (Phase 2 Stub)
///
/// Groups assertions by their `object` field (string representation) and
/// returns a representative from the largest group.
///
/// # Future Implementation (Phase 4)
///
/// Will integrate with VoteStore to:
/// - Count explicit votes per assertion
/// - Weight by agent TrustRank
/// - Consider multi-sig counts
///
/// # Resolution Strategy
///
/// 1. Group assertions by object value
/// 2. Select the group with the most members
/// 3. From that group, pick the most recent assertion
#[derive(Debug, Clone, Copy, Default)]
pub struct ConsensusLens;

impl Lens for ConsensusLens {
    #[instrument(skip(self, candidates), fields(candidates_count = candidates.len(), lens = "Consensus"))]
    fn resolve(&self, candidates: &[Assertion]) -> Resolution {
        if candidates.is_empty() {
            return Resolution::empty();
        }

        if candidates.len() == 1 {
            return Resolution::with_winner(candidates[0].clone(), 1, 1.0, 0.0);
        }

        // Group assertions by their object value
        // We use Debug format as a simple equality proxy
        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);
        }

        // Find the largest group
        let largest_group = groups.values().max_by_key(|v| v.len());

        match largest_group {
            Some(group) => {
                // From the largest group, pick the most recent
                let winner = group.iter().max_by_key(|a| a.timestamp).cloned().cloned();

                match winner {
                    Some(w) => {
                        let group_size = group.len();
                        let total = candidates.len();

                        // Confidence = proportion of candidates that agree
                        let confidence = group_size as f32 / total as f32;

                        let conflict = compute_conflict_score(candidates);
                        Resolution::with_winner(w, total, confidence, conflict)
                    }
                    None => Resolution::empty(),
                }
            }
            None => Resolution::empty(),
        }
    }

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

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

    fn create_assertion(subject: &str, value: f64, timestamp: u64) -> Assertion {
        AssertionBuilder::new().subject(subject).object_number(value).timestamp(timestamp).build()
    }

    #[test]
    fn test_empty_candidates() {
        let lens = ConsensusLens;
        let resolution = lens.resolve(&[]);

        assert!(resolution.winner.is_none());
    }

    #[test]
    fn test_selects_most_common_value() {
        let lens = ConsensusLens;

        // Three assertions say 100.0, one says 200.0
        let a1 = create_assertion("Agent1", 100.0, 1000);
        let a2 = create_assertion("Agent2", 100.0, 1100);
        let a3 = create_assertion("Agent3", 100.0, 1200);
        let outlier = create_assertion("Outlier", 200.0, 1300);

        let resolution = lens.resolve(&[a1, a2, a3, outlier]);

        assert!(resolution.winner.is_some());
        // Winner should have value 100.0
        match &resolution.winner.as_ref().map(|a| &a.object) {
            Some(ObjectValue::Number(v)) => assert!((*v - 100.0).abs() < f64::EPSILON),
            _ => panic!("Expected Number"),
        }
    }

    #[test]
    fn test_selects_newest_from_consensus_group() {
        let lens = ConsensusLens;

        let old = create_assertion("Old", 100.0, 1000);
        let new = create_assertion("New", 100.0, 2000);

        let resolution = lens.resolve(&[old, new.clone()]);

        // Both have same value, should pick newer
        assert!(resolution.winner.is_some());
        assert_eq!(resolution.winner.as_ref().map(|a| &a.subject), Some(&"New".to_string()));
    }

    #[test]
    fn test_confidence_reflects_agreement() {
        let lens = ConsensusLens;

        // 3 out of 4 agree
        let a1 = create_assertion("A1", 100.0, 1000);
        let a2 = create_assertion("A2", 100.0, 1100);
        let a3 = create_assertion("A3", 100.0, 1200);
        let outlier = create_assertion("Outlier", 200.0, 1300);

        let resolution = lens.resolve(&[a1, a2, a3, outlier]);

        // Confidence should be 3/4 = 0.75
        assert!((resolution.resolution_confidence - 0.75).abs() < f32::EPSILON);
    }

    #[test]
    fn test_full_consensus() {
        let lens = ConsensusLens;

        let a1 = create_assertion("A1", 100.0, 1000);
        let a2 = create_assertion("A2", 100.0, 1100);

        let resolution = lens.resolve(&[a1, a2]);

        // Full consensus = 1.0 confidence
        assert!((resolution.resolution_confidence - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_lens_name() {
        let lens = ConsensusLens;
        assert_eq!(lens.name(), "Consensus");
    }
}
