//! Aphoria Authority Lens - formalizes the authority-based conflict scoring.
//!
//! Wraps the existing scoring formula from `conflict.rs` into a proper
//! `stemedb_lens::Lens` implementation. This allows the authority resolution
//! logic to be used as a first-class Lens in Episteme queries.

use stemedb_core::types::{Assertion, SourceClass};
use stemedb_lens::{Lens, Resolution};

use crate::types::TierBreakdown;

/// Authority-based lens that resolves conflicts by source class tier.
///
/// Higher-authority sources (lower tier numbers) win. Uses the same formula
/// as `compute_conflict_score()` in `conflict.rs`:
///
/// ```text
/// normalized = 0.4 + (3.0 - min_tier) / 3.0 * 0.55
/// ```
///
/// Tier 0 (Regulatory) produces score ~0.95, Tier 3 (Expert) produces ~0.40.
pub struct AphoriaAuthorityLens;

impl Lens for AphoriaAuthorityLens {
    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 by tier, pick the winner from the highest-authority (lowest tier) group
        let mut best_tier = u8::MAX;
        let mut best_assertion: Option<&Assertion> = None;
        let mut best_confidence: f32 = 0.0;

        for assertion in candidates {
            let tier = assertion.source_class.tier();
            if tier < best_tier || (tier == best_tier && assertion.confidence > best_confidence) {
                best_tier = tier;
                best_assertion = Some(assertion);
                best_confidence = assertion.confidence;
            }
        }

        let winner = match best_assertion {
            Some(a) => a.clone(),
            None => return Resolution::empty(),
        };

        // Compute conflict score using the same formula as conflict.rs
        let conflict_score = authority_conflict_score(candidates);

        // Resolution confidence is based on how dominant the winning tier is
        let min_tier = best_tier as f32;
        let resolution_confidence = 0.4 + (3.0 - min_tier.min(3.0)) / 3.0 * 0.55;

        Resolution::with_winner(
            winner,
            candidates.len(),
            resolution_confidence.min(1.0),
            conflict_score,
        )
    }

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

/// Compute cross-tier conflict score for a set of assertions.
///
/// Uses the same normalized formula as `conflict.rs:compute_conflict_score()`:
/// `normalized = 0.4 + (3.0 - min_tier) / 3.0 * 0.55`
///
/// Returns 0.0 if all assertions are the same tier, higher values when
/// high-authority sources (Tier 0) conflict with low-authority (Tier 3+).
fn authority_conflict_score(candidates: &[Assertion]) -> f32 {
    if candidates.len() <= 1 {
        return 0.0;
    }

    let min_tier = candidates.iter().map(|a| a.source_class.tier()).min().unwrap_or(3);
    let max_tier = candidates.iter().map(|a| a.source_class.tier()).max().unwrap_or(3);

    if min_tier == max_tier {
        return 0.0; // Same tier, no authority conflict
    }

    // Tier distance maps to conflict intensity
    let tier_distance = (max_tier - min_tier) as f32;
    (tier_distance / 5.0).min(1.0) // Max 5 tiers apart (0-5)
}

/// Compute tier breakdown from a set of assertions.
///
/// Returns a sorted (by tier) list of tier breakdowns.
pub fn compute_tier_breakdown(assertions: &[Assertion]) -> Vec<TierBreakdown> {
    use std::collections::BTreeMap;

    let mut by_tier: BTreeMap<u8, (SourceClass, usize, f32)> = BTreeMap::new();

    for assertion in assertions {
        let tier = assertion.source_class.tier();
        let entry = by_tier.entry(tier).or_insert((assertion.source_class, 0, 0.0));
        entry.1 += 1;
        if assertion.confidence > entry.2 {
            entry.2 = assertion.confidence;
        }
    }

    by_tier
        .into_iter()
        .map(|(tier, (source_class, count, max_conf))| TierBreakdown {
            tier,
            source_class,
            assertion_count: count,
            max_confidence: max_conf,
        })
        .collect()
}

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

    #[test]
    fn test_empty_candidates() {
        let lens = AphoriaAuthorityLens;
        let result = lens.resolve(&[]);
        assert!(result.winner.is_none());
        assert_eq!(result.candidates_count, 0);
    }

    #[test]
    fn test_single_candidate() {
        let lens = AphoriaAuthorityLens;
        let assertion =
            AssertionBuilder::new().source_class(SourceClass::Regulatory).confidence(0.95).build();
        let result = lens.resolve(&[assertion]);
        assert!(result.winner.is_some());
        assert_eq!(result.candidates_count, 1);
    }

    #[test]
    fn test_authority_wins_over_lower_tier() {
        let lens = AphoriaAuthorityLens;
        let regulatory = AssertionBuilder::new()
            .subject("rfc://test")
            .source_class(SourceClass::Regulatory)
            .confidence(0.9)
            .build();
        let community = AssertionBuilder::new()
            .subject("code://test")
            .source_class(SourceClass::Community)
            .confidence(1.0)
            .build();

        let result = lens.resolve(&[community, regulatory]);
        let winner = result.winner.as_ref().expect("should have winner");
        assert_eq!(winner.source_class, SourceClass::Regulatory);
        assert_eq!(result.candidates_count, 2);
    }

    #[test]
    fn test_lens_scores_match_existing() {
        // Verify the normalized formula matches conflict.rs expectations
        // Tier 0 vs code → ~0.95
        let regulatory =
            AssertionBuilder::new().source_class(SourceClass::Regulatory).confidence(1.0).build();
        let community =
            AssertionBuilder::new().source_class(SourceClass::Community).confidence(1.0).build();

        let lens = AphoriaAuthorityLens;
        let result = lens.resolve(&[regulatory, community]);

        // Resolution confidence for Tier 0 winner should be ~0.95
        assert!(
            result.resolution_confidence > 0.9,
            "Expected >0.9, got {}",
            result.resolution_confidence
        );
    }

    #[test]
    fn test_tier_breakdown() {
        let assertions = vec![
            AssertionBuilder::new().source_class(SourceClass::Regulatory).confidence(0.95).build(),
            AssertionBuilder::new().source_class(SourceClass::Regulatory).confidence(0.9).build(),
            AssertionBuilder::new().source_class(SourceClass::Community).confidence(0.7).build(),
        ];

        let breakdown = compute_tier_breakdown(&assertions);
        assert_eq!(breakdown.len(), 2);
        assert_eq!(breakdown[0].tier, 0); // Regulatory
        assert_eq!(breakdown[0].assertion_count, 2);
        assert!((breakdown[0].max_confidence - 0.95).abs() < f32::EPSILON);
        assert_eq!(breakdown[1].assertion_count, 1);
    }
}
