//! Pure conflict checking logic without persistence.
//!
//! Provides standalone functions for detecting conflicts between claims and
//! authoritative sources using concept index lookups and alias resolution.

use std::collections::HashMap;

use stemedb_core::types::SourceClass;
use tracing::info;

use crate::config::AphoriaConfig;
use crate::types::{
    ConflictResult, ConflictTrace, ConflictingSource, ExtractedClaim, PolicySourceInfo,
    PredicateAliasSet, Verdict,
};

use super::concept_index::ConceptIndex;

/// Check for conflicts between extracted claims and authoritative sources (pure function).
///
/// This is a standalone function that doesn't require `LocalEpisteme`.
/// It uses tail-path matching via `ConceptIndex` to find conflicts across different
/// URI schemes.
///
/// # Arguments
/// * `claims` - Extracted claims from source code
/// * `index` - In-memory concept index built from authoritative corpus
/// * `aliases` - In-memory alias map from policies
/// * `pack_sources` - Mapping from assertion subject to policy source info
/// * `config` - Configuration with thresholds
/// * `debug` - If true, populate ConflictTrace for each result
///
/// # Returns
/// Vector of conflict results for claims that conflict with authoritative sources.
/// Check for conflicts between extracted claims and authoritative sources (pure function).
///
/// This version uses predicate aliases from config only.
#[allow(dead_code)]
pub fn check_conflicts_pure(
    claims: &[ExtractedClaim],
    index: &ConceptIndex,
    aliases: &HashMap<String, String>,
    pack_sources: &HashMap<String, PolicySourceInfo>,
    config: &AphoriaConfig,
    debug: bool,
) -> Vec<ConflictResult> {
    // Get predicate aliases from config
    let predicate_aliases = config.predicate_aliases.to_alias_sets();
    check_conflicts_with_predicate_aliases(
        claims,
        index,
        aliases,
        pack_sources,
        &predicate_aliases,
        config,
        debug,
    )
}

/// Check for conflicts with explicit predicate aliases.
///
/// This variant allows passing predicate aliases explicitly, which is useful
/// when aliases come from multiple sources (config + Trust Packs).
pub fn check_conflicts_with_predicate_aliases(
    claims: &[ExtractedClaim],
    index: &ConceptIndex,
    aliases: &HashMap<String, String>,
    pack_sources: &HashMap<String, PolicySourceInfo>,
    predicate_aliases: &[PredicateAliasSet],
    config: &AphoriaConfig,
    debug: bool,
) -> Vec<ConflictResult> {
    let mut results = Vec::new();

    for claim in claims {
        // 1. Try to resolve alias first
        let resolved_path = aliases.get(&claim.concept_path).map(|s| s.as_str());

        // 2. Normalize the predicate using predicate aliases
        let normalized_predicate =
            ConceptIndex::normalize_predicate(&claim.predicate, predicate_aliases);

        // 3. Look up authoritative assertions
        let auth_assertions = if let Some(path) = resolved_path {
            // If alias exists, use the aliased path (assumed to be authoritative)
            // But ConceptIndex is keyed by tail path.
            // If we have the full path, we can try to make a key from it.
            if let Some(key) = ConceptIndex::make_key(path, normalized_predicate) {
                index.entries.get(&key)
            } else {
                None
            }
        } else {
            // Fallback to tail-path matching with normalized predicate
            index.lookup_with_aliases(&claim.concept_path, &claim.predicate, predicate_aliases)
        };

        let auth_assertions = match auth_assertions {
            Some(assertions) => assertions,
            None => continue, // No authoritative coverage for this concept
        };

        // Find conflicting authoritative sources
        let mut conflicts = Vec::new();
        let mut primary_authority: Option<(&str, SourceClass)> = None;

        for assertion in auth_assertions {
            // Skip if it's our own assertion (same source class)
            // Or if it's a Manual policy override that agrees with us?
            // Actually, if a policy overrides something, it usually provides an assertion.
            // If the assertion matches our claim, it's not a conflict.
            // If it differs, it is.

            if assertion.source_class == SourceClass::Expert {
                // If this is a Manual/Policy assertion, we treat it as authoritative if it differs?
                // Or maybe we treat it as "overriding" the RFC?
                // For now, treat it like any other assertion.
            }

            // Check if value differs (for conflict reporting)
            if assertion.object != claim.value {
                // Only consider Tier 0-2 (RFC/Vendor) AND Tier 3 (Policy/Expert) as authoritative
                // Policies are usually Tier 3.
                if assertion.source_class.tier() <= 3 {
                    // Track highest-tier (lowest number) authority for trace
                    if primary_authority.is_none()
                        || assertion.source_class.tier()
                            < primary_authority.map(|(_, sc)| sc.tier()).unwrap_or(99)
                    {
                        primary_authority = Some((&assertion.subject, assertion.source_class));
                    }

                    let rfc_citation = ConflictingSource::extract_citation(&assertion.subject);
                    // Look up policy source info if this assertion came from a Trust Pack
                    let policy_source = pack_sources.get(&assertion.subject).cloned();

                    conflicts.push(ConflictingSource {
                        path: assertion.subject.clone(),
                        source_class: assertion.source_class,
                        value: assertion.object.clone(),
                        confidence: assertion.confidence,
                        rfc_citation,
                        policy_source,
                    });
                }
            }
        }

        if conflicts.is_empty() {
            continue;
        }

        // Compute conflict score
        let conflict_score = compute_conflict_score(&conflicts, claim.confidence);

        // Determine verdict
        let verdict = if conflict_score >= config.thresholds.block {
            Verdict::Block
        } else if conflict_score >= config.thresholds.flag {
            Verdict::Flag
        } else {
            Verdict::Pass
        };

        // Build debug trace if enabled
        let trace = if debug {
            primary_authority.map(|(auth_path, source_class)| {
                // Format code claim: concept_path = value
                let code_claim = format!("{} = {:?}", claim.concept_path, claim.value);
                // Format authority match: path = expected_value
                let auth_match = format!(
                    "{} = {:?}",
                    auth_path,
                    conflicts.first().map(|c| &c.value).unwrap_or(&claim.value)
                );
                ConflictTrace::new(&code_claim, &auth_match, source_class, conflict_score, verdict)
            })
        } else {
            None
        };

        results.push(ConflictResult {
            claim: claim.clone(),
            conflicts,
            conflict_score,
            verdict,
            acknowledged: None,
            trace,
        });
    }

    info!(
        conflicts = results.len(),
        blocks = results.iter().filter(|r| r.verdict == Verdict::Block).count(),
        flags = results.iter().filter(|r| r.verdict == Verdict::Flag).count(),
        "Pure conflict check complete"
    );

    results
}

/// Compute conflict score based on authoritative sources and claim confidence.
///
/// The score uses two approaches and takes the maximum:
///
/// 1. **Boosted score**: `max_tier_weight * (1.0 - code_weight) * max_confidence`
///    where code_weight = Expert (Tier 3) = 0.5. This is low unless the
///    authoritative source has very high authority weight.
///
/// 2. **Normalized score**: Linear mapping from tier distance to score:
///    - Tier 0 (Regulatory) vs code → 0.95 (above BLOCK threshold 0.7)
///    - Tier 1 (Clinical)   vs code → 0.77 (above BLOCK threshold 0.7)
///    - Tier 2 (Observational) vs code → 0.58 (above FLAG threshold 0.4)
///    - Tier 3 (same tier)  vs code → 0.40 (at FLAG threshold)
///
/// The final score is capped at 1.0.
pub fn compute_conflict_score(conflicts: &[ConflictingSource], _claim_confidence: f32) -> f32 {
    if conflicts.is_empty() {
        return 0.0;
    }

    // Get max tier weight from conflicting sources
    let max_tier_weight = conflicts
        .iter()
        .map(|c| c.source_class.authority_weight())
        .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
        .unwrap_or(0.0);

    // Code claims are Expert (Tier 3) = 0.5 weight
    let code_weight = SourceClass::Expert.authority_weight();

    // Base conflict score from tier spread
    let base_score = max_tier_weight * (1.0 - code_weight);

    // Boost by authoritative source confidence
    let max_confidence = conflicts
        .iter()
        .map(|c| c.confidence)
        .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
        .unwrap_or(1.0);

    let boosted_score = base_score * max_confidence;

    // Normalize: tier spread 0→3 maps to 0.4→0.95
    let min_tier = conflicts.iter().map(|c| c.source_class.tier()).min().unwrap_or(3) as f32;
    let normalized = 0.4 + (3.0 - min_tier) / 3.0 * 0.55;

    normalized.max(boosted_score).min(1.0)
}
