//! Layered Consensus Lens: Per-source-class consensus resolution.
//!
//! This lens provides visibility into what each authority tier says,
//! rather than collapsing everything into a single winner.
//!
//! # Use Case: Consumer Health
//!
//! Query "semaglutide muscle_loss" and see:
//! - Tier 0 (Regulatory): [no data]
//! - Tier 1 (Clinical): "Significant loss" (12 sources)
//! - Tier 5 (Anecdotal): "Minimal loss" (200 sources)
//!
//! The overall winner comes from the highest-authority tier present (Tier 1),
//! but the consumer can see that anecdotal sources disagree.
//!
//! # Algorithm
//!
//! 1. Group candidates by `source_class.tier()`
//! 2. For each tier, run `ConsensusLens::resolve()` to get within-tier winner
//! 3. Compute per-tier conflict score
//! 4. Overall winner = winner from lowest tier number (highest authority)
//! 5. Cross-tier conflict = do tier winners agree on the same object value?

use crate::consensus::ConsensusLens;
use crate::traits::{LayeredLens, LayeredResolution, Lens, Resolution, TierResolution};
use std::collections::HashMap;
use stemedb_core::types::{Assertion, SourceClass};
use tracing::instrument;

/// Layered Consensus Lens: Provides per-tier resolution results.
///
/// # Example
///
/// ```rust,ignore
/// use stemedb_lens::{LayeredConsensusLens, LayeredLens};
/// use stemedb_core::types::Assertion;
///
/// let lens = LayeredConsensusLens::new();
/// let assertions: Vec<Assertion> = vec![/* ... */];
/// let result = lens.resolve_layered(&assertions);
///
/// for tier in &result.tiers {
///     println!("Tier {}: {:?} candidates", tier.tier, tier.candidates_count);
/// }
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct LayeredConsensusLens;

impl LayeredConsensusLens {
    /// Create a new LayeredConsensusLens.
    pub fn new() -> Self {
        Self
    }

    /// Group assertions by their source class tier.
    fn group_by_tier(candidates: &[Assertion]) -> HashMap<u8, Vec<&Assertion>> {
        let mut groups: HashMap<u8, Vec<&Assertion>> = HashMap::new();

        for assertion in candidates {
            let tier = assertion.source_class.tier();
            groups.entry(tier).or_default().push(assertion);
        }

        groups
    }

    /// Get the SourceClass for a given tier number.
    fn tier_to_source_class(tier: u8) -> SourceClass {
        match tier {
            0 => SourceClass::Regulatory,
            1 => SourceClass::Clinical,
            2 => SourceClass::Observational,
            3 => SourceClass::Expert,
            4 => SourceClass::Community,
            _ => SourceClass::Anecdotal,
        }
    }

    /// Compute cross-tier conflict score.
    ///
    /// Measures disagreement between tier winners:
    /// - 0.0: All tier winners have the same object value
    /// - 1.0: Tier winners have completely different object values
    ///
    /// # Algorithm
    ///
    /// Groups tier winners by their object value and computes normalized entropy.
    /// If only one tier has a winner, conflict is 0.0 (no disagreement possible).
    fn compute_cross_tier_conflict(tier_winners: &[&Assertion]) -> f32 {
        if tier_winners.len() <= 1 {
            return 0.0;
        }

        // Group winners by their object value (using Debug format as equality proxy)
        let mut value_counts: HashMap<String, usize> = HashMap::new();
        for winner in tier_winners {
            let key = format!("{:?}", winner.object);
            *value_counts.entry(key).or_default() += 1;
        }

        let num_unique_values = value_counts.len();
        if num_unique_values == 1 {
            // All winners agree
            return 0.0;
        }

        // Compute normalized entropy
        let total = tier_winners.len() as f32;
        let mut entropy = 0.0f32;

        for &count in value_counts.values() {
            if count > 0 {
                let p = count as f32 / total;
                entropy -= p * p.ln();
            }
        }

        // Normalize by max entropy (uniform distribution)
        let max_entropy = (num_unique_values as f32).ln();
        if max_entropy > 0.0 {
            (entropy / max_entropy).min(1.0)
        } else {
            0.0
        }
    }
}

impl LayeredLens for LayeredConsensusLens {
    #[instrument(skip(self, candidates), fields(candidates_count = candidates.len(), lens = "LayeredConsensus"))]
    fn resolve_layered(&self, candidates: &[Assertion]) -> LayeredResolution {
        if candidates.is_empty() {
            return LayeredResolution::empty();
        }

        // Group by tier
        let tier_groups = Self::group_by_tier(candidates);

        // Resolve each tier
        let consensus_lens = ConsensusLens;
        let mut tier_resolutions: Vec<TierResolution> = Vec::new();

        // Process tiers in order (0 to 5)
        for tier in 0..=5u8 {
            if let Some(tier_candidates) = tier_groups.get(&tier) {
                // Convert refs to owned for ConsensusLens
                let owned: Vec<Assertion> = tier_candidates.iter().map(|a| (*a).clone()).collect();
                let resolution = consensus_lens.resolve(&owned);

                let tier_resolution = TierResolution {
                    tier,
                    source_class: Self::tier_to_source_class(tier),
                    winner: resolution.winner,
                    candidates_count: resolution.candidates_count,
                    conflict_score: resolution.conflict_score,
                    resolution_confidence: resolution.resolution_confidence,
                };

                tier_resolutions.push(tier_resolution);
            }
        }

        // Overall winner = winner from highest-authority tier (lowest tier number)
        let overall_winner = tier_resolutions.iter().find_map(|tr| tr.winner.clone());

        // Collect tier winners for cross-tier conflict calculation
        let tier_winners: Vec<&Assertion> =
            tier_resolutions.iter().filter_map(|tr| tr.winner.as_ref()).collect();

        let overall_conflict_score = Self::compute_cross_tier_conflict(&tier_winners);

        LayeredResolution {
            tiers: tier_resolutions,
            overall_winner,
            overall_conflict_score,
            total_candidates: candidates.len(),
        }
    }

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

/// Implement standard Lens trait for compatibility.
///
/// Returns the overall winner as a regular Resolution.
/// Use `resolve_layered()` for the richer per-tier results.
impl Lens for LayeredConsensusLens {
    #[instrument(skip(self, candidates), fields(candidates_count = candidates.len(), lens = "LayeredConsensus"))]
    fn resolve(&self, candidates: &[Assertion]) -> Resolution {
        let layered = self.resolve_layered(candidates);

        match layered.overall_winner {
            Some(winner) => {
                // Aggregate confidence from the winning tier
                let winning_tier = layered.tiers.first();
                let confidence = winning_tier.map(|t| t.resolution_confidence).unwrap_or(1.0);

                Resolution::with_winner(
                    winner,
                    layered.total_candidates,
                    confidence,
                    layered.overall_conflict_score,
                )
            }
            None => Resolution::empty(),
        }
    }

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

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

    /// Create an assertion with the specified source class and object value.
    fn create_assertion(source_class: SourceClass, value: &str, timestamp: u64) -> Assertion {
        AssertionBuilder::new()
            .source_class(source_class)
            .object_text(value)
            .timestamp(timestamp)
            .build()
    }

    #[test]
    fn test_layered_empty_candidates() {
        let lens = LayeredConsensusLens::new();
        let result = lens.resolve_layered(&[]);

        assert!(result.overall_winner.is_none());
        assert!(result.tiers.is_empty());
        assert_eq!(result.total_candidates, 0);
        assert!((result.overall_conflict_score - 0.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_layered_single_tier() {
        // All candidates are from the same source class (Expert, Tier 3)
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Expert, "safe", 1000),
            create_assertion(SourceClass::Expert, "safe", 1100),
            create_assertion(SourceClass::Expert, "risky", 1200),
        ];

        let result = lens.resolve_layered(&assertions);

        // Should have exactly one tier
        assert_eq!(result.tiers.len(), 1);
        assert_eq!(result.tiers[0].tier, 3); // Expert = Tier 3
        assert_eq!(result.tiers[0].candidates_count, 3);

        // Winner should be "safe" (2 vs 1)
        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Text("safe".to_string()));
        }

        // Cross-tier conflict should be 0 (only one tier)
        assert!((result.overall_conflict_score - 0.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_layered_multi_tier_agreement() {
        // Tier 0 (Regulatory) and Tier 5 (Anecdotal) both say "safe"
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Regulatory, "safe", 1000),
            create_assertion(SourceClass::Anecdotal, "safe", 1100),
            create_assertion(SourceClass::Anecdotal, "safe", 1200),
        ];

        let result = lens.resolve_layered(&assertions);

        // Should have two tiers (0 and 5)
        assert_eq!(result.tiers.len(), 2);
        assert_eq!(result.tiers[0].tier, 0); // Regulatory first
        assert_eq!(result.tiers[1].tier, 5); // Anecdotal second

        // Overall winner from Tier 0
        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Text("safe".to_string()));
            assert_eq!(winner.source_class, SourceClass::Regulatory);
        }

        // Cross-tier conflict should be low (both agree on "safe")
        assert!(
            result.overall_conflict_score < 0.1,
            "Expected low conflict, got {}",
            result.overall_conflict_score
        );
    }

    #[test]
    fn test_layered_multi_tier_disagreement() {
        // Tier 1 (Clinical) says "safe", Tier 5 (Anecdotal) says "dangerous"
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Clinical, "safe", 1000),
            create_assertion(SourceClass::Clinical, "safe", 1100),
            create_assertion(SourceClass::Anecdotal, "dangerous", 1200),
            create_assertion(SourceClass::Anecdotal, "dangerous", 1300),
            create_assertion(SourceClass::Anecdotal, "dangerous", 1400),
        ];

        let result = lens.resolve_layered(&assertions);

        // Should have two tiers
        assert_eq!(result.tiers.len(), 2);

        // Tier 1 (Clinical) is higher authority, so its winner is overall winner
        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Text("safe".to_string()));
            assert_eq!(winner.source_class, SourceClass::Clinical);
        }

        // Cross-tier conflict should be high (tiers disagree)
        assert!(
            result.overall_conflict_score > 0.5,
            "Expected high conflict, got {}",
            result.overall_conflict_score
        );
    }

    #[test]
    fn test_layered_overall_winner_from_highest_authority() {
        // Tier 0 has 1 assertion, Tier 5 has 1000 assertions
        // Tier 0 should still win (highest authority)
        let lens = LayeredConsensusLens::new();

        let mut assertions = vec![create_assertion(SourceClass::Regulatory, "approved", 1000)];

        // Add many anecdotal assertions
        for i in 0..100 {
            assertions.push(create_assertion(SourceClass::Anecdotal, "questionable", 2000 + i));
        }

        let result = lens.resolve_layered(&assertions);

        // Overall winner should be from Tier 0 (Regulatory) despite being outnumbered
        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Text("approved".to_string()));
            assert_eq!(winner.source_class, SourceClass::Regulatory);
        }

        // Verify tier counts
        let tier_0 = result.tiers.iter().find(|t| t.tier == 0);
        let tier_5 = result.tiers.iter().find(|t| t.tier == 5);

        assert!(tier_0.is_some());
        assert_eq!(tier_0.map(|t| t.candidates_count).unwrap_or(0), 1);

        assert!(tier_5.is_some());
        assert_eq!(tier_5.map(|t| t.candidates_count).unwrap_or(0), 100);
    }

    #[test]
    fn test_layered_lens_trait_compatibility() {
        // Test that the standard Lens trait works
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Clinical, "effective", 1000),
            create_assertion(SourceClass::Clinical, "effective", 1100),
        ];

        let resolution = lens.resolve(&assertions);

        assert!(resolution.winner.is_some());
        assert_eq!(resolution.candidates_count, 2);
    }

    #[test]
    fn test_layered_within_tier_conflict() {
        // Tier 1 has high internal conflict (split opinions)
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Clinical, "safe", 1000),
            create_assertion(SourceClass::Clinical, "dangerous", 1100),
        ];

        let result = lens.resolve_layered(&assertions);

        assert_eq!(result.tiers.len(), 1);

        // Within-tier conflict should be non-zero (assertions have different confidences
        // by default, but same confidence here - conflict comes from object disagreement
        // which is reflected in the resolution confidence, not conflict_score)
        let tier = &result.tiers[0];
        assert_eq!(tier.candidates_count, 2);

        // Resolution confidence should reflect the split (50/50)
        assert!(tier.resolution_confidence < 0.6, "Expected low confidence for 50/50 split");
    }

    #[test]
    fn test_layered_all_tiers_present() {
        // One assertion from each tier
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            create_assertion(SourceClass::Regulatory, "tier0", 1000),
            create_assertion(SourceClass::Clinical, "tier1", 1100),
            create_assertion(SourceClass::Observational, "tier2", 1200),
            create_assertion(SourceClass::Expert, "tier3", 1300),
            create_assertion(SourceClass::Community, "tier4", 1400),
            create_assertion(SourceClass::Anecdotal, "tier5", 1500),
        ];

        let result = lens.resolve_layered(&assertions);

        // All 6 tiers should be present
        assert_eq!(result.tiers.len(), 6);

        // Verify tiers are in order
        for (i, tier) in result.tiers.iter().enumerate() {
            assert_eq!(tier.tier, i as u8);
        }

        // Overall winner should be from Tier 0
        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Text("tier0".to_string()));
        }
    }

    #[test]
    fn test_layered_lens_name() {
        let lens = LayeredConsensusLens::new();
        assert_eq!(<LayeredConsensusLens as LayeredLens>::name(&lens), "LayeredConsensus");
        assert_eq!(<LayeredConsensusLens as Lens>::name(&lens), "LayeredConsensus");
    }

    #[test]
    fn test_layered_numeric_values() {
        // Test with numeric object values
        let lens = LayeredConsensusLens::new();
        let assertions = vec![
            AssertionBuilder::new()
                .source_class(SourceClass::Clinical)
                .object_number(100.0)
                .timestamp(1000)
                .build(),
            AssertionBuilder::new()
                .source_class(SourceClass::Clinical)
                .object_number(100.0)
                .timestamp(1100)
                .build(),
            AssertionBuilder::new()
                .source_class(SourceClass::Anecdotal)
                .object_number(200.0)
                .timestamp(1200)
                .build(),
        ];

        let result = lens.resolve_layered(&assertions);

        assert!(result.overall_winner.is_some());
        if let Some(winner) = &result.overall_winner {
            assert_eq!(winner.object, ObjectValue::Number(100.0));
        }

        // Cross-tier conflict should be high (100.0 vs 200.0)
        assert!(result.overall_conflict_score > 0.5);
    }
}
