//! Skeptic Lens: Surfaces conflict instead of resolving it.
//!
//! Unlike other lenses that pick a winner, SkepticLens returns the entire
//! conflict landscape. It enables "Trust but Verify" UX where users see
//! competing claims, their relative support, and who disagrees.
//!
//! # Design Philosophy
//!
//! Following the "Deep Module" principle:
//! - Simple interface: `analyze(&[Assertion])` returns conflict analysis
//! - Complex implementation: Groups by value, calculates entropy, ranks claims
//! - Integrates with VoteStore and TrustRankStore for weighted analysis
//!
//! # Conflict Score
//!
//! Uses normalized Shannon entropy to measure disagreement:
//! - 0.0 = unanimous (all weight on one claim)
//! - 1.0 = maximum chaos (equal weight across all claims)
//!
//! # Example
//!
//! ```ignore
//! use stemedb_lens::SkepticLens;
//! use stemedb_storage::{HybridStore, GenericVoteStore};
//! use std::sync::Arc;
//!
//! let store = HybridStore::open("./data").await?;
//! let vote_store = Arc::new(GenericVoteStore::new(store));
//! let lens = SkepticLens::new(vote_store);
//!
//! let analysis = lens.analyze(&candidates).await;
//! println!("Status: {:?}, Conflict: {:.2}", analysis.status, analysis.conflict_score);
//! for claim in &analysis.claims {
//!     println!("  {} - {:.0}% support", claim.value, claim.weight_share * 100.0);
//! }
//! ```

mod analysis;

use std::sync::Arc;
use stemedb_storage::trust_rank_store::TrustRankStore;
use stemedb_storage::vote_store::VoteStore;

/// Skeptic Lens: Returns conflict landscape instead of picking a winner.
///
/// # Resolution Strategy
///
/// 1. Group assertions by their object value
/// 2. For each group, calculate aggregate vote weight
/// 3. Calculate normalized Shannon entropy as conflict score
/// 4. Build ClaimSummary for each group with supporting agents
/// 5. Return all claims ranked by weight_share
///
/// # Status Determination
///
/// | Conflict Score | Status |
/// |----------------|--------|
/// | < 0.1 | Unanimous |
/// | < 0.4 | Agreed |
/// | >= 0.4 | Contested |
pub struct SkepticLens<V, T> {
    vote_store: Arc<V>,
    trust_store: Arc<T>,
}

impl<V: VoteStore, T: TrustRankStore> SkepticLens<V, T> {
    /// Create a new SkepticLens with the given VoteStore and TrustRankStore.
    pub fn new(vote_store: Arc<V>, trust_store: Arc<T>) -> Self {
        Self { vote_store, trust_store }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::traits::AnalysisLens;
    use std::sync::Arc;
    use stemedb_core::testing::AssertionBuilder;
    use stemedb_core::types::{Assertion, ObjectValue, ResolutionStatus, Vote};
    use stemedb_storage::{GenericTrustRankStore, GenericVoteStore, HybridStore};

    fn create_assertion(subject: &str, value: f64, confidence: f32) -> Assertion {
        AssertionBuilder::new()
            .subject(subject)
            .predicate("test_predicate")
            .object_number(value)
            .confidence(confidence)
            .build()
    }

    fn create_text_assertion(subject: &str, text: &str, confidence: f32) -> Assertion {
        AssertionBuilder::new()
            .subject(subject)
            .predicate("test_predicate")
            .object_text(text)
            .confidence(confidence)
            .build()
    }

    #[tokio::test]
    async fn test_empty_candidates() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        let analysis = lens.analyze(&[]).await;

        assert_eq!(analysis.status, ResolutionStatus::Unanimous);
        assert!((analysis.conflict_score - 0.0).abs() < f32::EPSILON);
        assert!(analysis.claims.is_empty());
        assert_eq!(analysis.candidates_count, 0);
    }

    #[tokio::test]
    async fn test_single_candidate_is_unanimous() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        let assertion = create_assertion("Tesla", 100.0, 0.9);
        let analysis = lens.analyze(&[assertion]).await;

        assert_eq!(analysis.status, ResolutionStatus::Unanimous);
        assert!((analysis.conflict_score - 0.0).abs() < f32::EPSILON);
        assert_eq!(analysis.claims.len(), 1);
        assert!((analysis.claims[0].weight_share - 1.0).abs() < f32::EPSILON);
    }

    #[tokio::test]
    async fn test_same_value_is_unanimous() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        // Three assertions all claiming the same value
        let a1 = create_assertion("Tesla", 100.0, 0.9);
        let a2 = create_assertion("Tesla", 100.0, 0.8);
        let a3 = create_assertion("Tesla", 100.0, 0.7);
        let analysis = lens.analyze(&[a1, a2, a3]).await;

        assert_eq!(analysis.status, ResolutionStatus::Unanimous);
        assert!((analysis.conflict_score - 0.0).abs() < f32::EPSILON);
        assert_eq!(analysis.claims.len(), 1);
        assert_eq!(analysis.claims[0].assertion_count, 3);
    }

    #[tokio::test]
    async fn test_50_50_split_is_contested() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        // Two claims with equal confidence (no votes)
        let a1 = create_assertion("Tesla", 100.0, 0.5);
        let a2 = create_assertion("Tesla", 200.0, 0.5);
        let analysis = lens.analyze(&[a1, a2]).await;

        // 50/50 split should have max entropy for 2 options = 1.0
        assert_eq!(analysis.status, ResolutionStatus::Contested);
        assert!((analysis.conflict_score - 1.0).abs() < 0.01);
        assert_eq!(analysis.claims.len(), 2);
    }

    #[tokio::test]
    async fn test_strong_majority_is_agreed() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        // 95/5 split - needs to be more extreme to get low entropy
        let a1 = create_assertion("Tesla", 100.0, 0.95);
        let a2 = create_assertion("Tesla", 200.0, 0.05);
        let analysis = lens.analyze(&[a1, a2]).await;

        // 95/5 split has entropy ≈ 0.286, which is < 0.4 (Agreed)
        assert!(analysis.conflict_score < 0.4);
        assert!(
            analysis.status == ResolutionStatus::Agreed
                || analysis.status == ResolutionStatus::Unanimous
        );
    }

    #[tokio::test]
    async fn test_claims_sorted_by_weight() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        let a1 = create_assertion("Tesla", 100.0, 0.3); // Lowest
        let a2 = create_assertion("Tesla", 200.0, 0.9); // Highest
        let a3 = create_assertion("Tesla", 300.0, 0.5); // Middle
        let analysis = lens.analyze(&[a1, a2, a3]).await;

        assert_eq!(analysis.claims.len(), 3);
        // Should be sorted descending by weight_share
        assert!(analysis.claims[0].weight_share >= analysis.claims[1].weight_share);
        assert!(analysis.claims[1].weight_share >= analysis.claims[2].weight_share);
    }

    #[tokio::test]
    async fn test_text_value_grouping() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        let a1 = create_text_assertion("Drug", "Effective", 0.6);
        let a2 = create_text_assertion("Drug", "Ineffective", 0.4);
        let a3 = create_text_assertion("Drug", "Effective", 0.5); // Same as a1
        let analysis = lens.analyze(&[a1, a2, a3]).await;

        // Should group into 2 claims: "Effective" (2 assertions) and "Ineffective" (1)
        assert_eq!(analysis.claims.len(), 2);
        assert_eq!(analysis.candidates_count, 3);

        // Find the "Effective" claim
        let effective_claim = analysis
            .claims
            .iter()
            .find(|c| matches!(&c.value, ObjectValue::Text(t) if t == "Effective"));
        assert!(effective_claim.is_some());
        assert_eq!(effective_claim.map(|c| c.assertion_count).unwrap_or(0), 2);
    }

    #[tokio::test]
    async fn test_conflict_score_calculation() {
        // Test the entropy calculation directly
        // 50/50 split: max entropy for 2 options = 1.0
        let weights_equal = vec![(ObjectValue::Number(1.0), 0.5), (ObjectValue::Number(2.0), 0.5)];
        let score = SkepticLens::<GenericVoteStore<HybridStore>, GenericTrustRankStore<HybridStore>>::calculate_conflict_score(&weights_equal);
        assert!((score - 1.0).abs() < 0.01);

        // 100/0 split: zero entropy
        let weights_unanimous =
            vec![(ObjectValue::Number(1.0), 1.0), (ObjectValue::Number(2.0), 0.0)];
        let score = SkepticLens::<GenericVoteStore<HybridStore>, GenericTrustRankStore<HybridStore>>::calculate_conflict_score(&weights_unanimous);
        assert!((score - 0.0).abs() < 0.01);

        // Single claim: unanimous
        let weights_single = vec![(ObjectValue::Number(1.0), 1.0)];
        let score = SkepticLens::<GenericVoteStore<HybridStore>, GenericTrustRankStore<HybridStore>>::calculate_conflict_score(&weights_single);
        assert!((score - 0.0).abs() < 0.01);
    }

    #[tokio::test]
    async fn test_lens_name() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(vote_store, trust_store);

        assert_eq!(lens.name(), "Skeptic");
    }

    #[tokio::test]
    async fn test_with_votes() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
        let trust_store = Arc::new(GenericTrustRankStore::new(store));
        let lens = SkepticLens::new(Arc::clone(&vote_store), trust_store);

        // Create two assertions with different values
        let a1 = create_assertion("Tesla", 100.0, 0.5);
        let a2 = create_assertion("Tesla", 200.0, 0.5);

        // Add votes to make a1 a strong winner
        let hash1 =
            SkepticLens::<GenericVoteStore<HybridStore>, GenericTrustRankStore<HybridStore>>::compute_assertion_hash(&a1);
        let hash2 =
            SkepticLens::<GenericVoteStore<HybridStore>, GenericTrustRankStore<HybridStore>>::compute_assertion_hash(&a2);

        // a1 gets 10 votes totaling 9.0 weight (strong majority)
        for i in 0..10 {
            let vote = Vote {
                assertion_hash: hash1,
                agent_id: [i as u8; 32],
                weight: 0.9,
                signature: [0u8; 64],
                timestamp: 1000 + i as u64,
                source_url: None,
                observed_context: None,
            };
            vote_store.put_vote(&vote, "Tesla").await.expect("put vote");
        }

        // a2 gets 1 vote with 0.5 weight (minority)
        let vote = Vote {
            assertion_hash: hash2,
            agent_id: [20u8; 32],
            weight: 0.5,
            signature: [0u8; 64],
            timestamp: 2000,
            source_url: None,
            observed_context: None,
        };
        vote_store.put_vote(&vote, "Tesla").await.expect("put vote");

        let analysis = lens.analyze(&[a1, a2]).await;

        // a1 should have higher weight_share
        assert_eq!(analysis.claims.len(), 2);
        let first_claim = &analysis.claims[0];
        let second_claim = &analysis.claims[1];

        assert!(
            first_claim.weight_share > second_claim.weight_share,
            "First claim ({:.2}) should have higher weight than second ({:.2})",
            first_claim.weight_share,
            second_claim.weight_share
        );

        // With 9.0 / 9.5 ≈ 0.947 for first claim, entropy should be very low
        // Actually: -0.947*log2(0.947) - 0.053*log2(0.053) ≈ 0.296
        assert!(
            analysis.conflict_score < 0.4,
            "Conflict score {} should be < 0.4 for strong majority",
            analysis.conflict_score
        );
    }
}
