//! Query operations for LocalEpisteme.
//!
//! Handles conflict checking and assertion lookups.

use stemedb_core::types::Assertion;
use stemedb_storage::{KVStore, PackSourceStore};
use tracing::{debug, info, instrument, warn};

use crate::config::AphoriaConfig;
use crate::types::{
    AcknowledgmentInfo, ConflictResult, ConflictingSource, ExtractedClaim, PolicySourceInfo,
    Verdict,
};
use crate::AphoriaError;

use super::super::concept_index::ConceptIndex;
use super::super::conflict::compute_conflict_score;
use super::super::corpus::current_timestamp;
use super::super::helpers::format_timestamp;
use super::LocalEpisteme;

impl LocalEpisteme {
    /// Check for conflicts between extracted claims and authoritative sources.
    ///
    /// Uses tail-path matching via `ConceptIndex` to find conflicts across different
    /// URI schemes. For example, a code claim at `code://rust/myapp/tls/cert_verification`
    /// will match authoritative assertions at `rfc://5246/tls/cert_verification`.
    ///
    /// When `config.aliases.auto_create_aliases` is enabled, this method will
    /// automatically persist aliases for matched concepts, enabling faster future
    /// queries via `QueryEngine` with `resolve_aliases: true`.
    ///
    /// Also looks up prior acknowledgments - if a concept has been acknowledged,
    /// its verdict will be `Verdict::Ack` instead of `Block`/`Flag`.
    #[instrument(skip(self, claims, config, index), fields(claim_count = claims.len()))]
    pub async fn check_conflicts(
        &self,
        claims: &[ExtractedClaim],
        config: &AphoriaConfig,
        index: &ConceptIndex,
    ) -> Result<Vec<ConflictResult>, AphoriaError> {
        let mut results = Vec::new();
        let mut aliases_created = 0usize;
        let mut acked_count = 0usize;
        let timestamp = current_timestamp();
        let agent_id = self.agent_id();

        // Fetch all acknowledgments upfront and build a lookup map by subject (concept path)
        let acks = self.fetch_acknowledgments().await?;
        let ack_map: std::collections::HashMap<&str, &Assertion> =
            acks.iter().map(|a| (a.subject.as_str(), a)).collect();

        // Merge predicate aliases from config and imported packs
        let mut all_aliases = config.predicate_aliases.to_alias_sets();
        all_aliases.extend(self.predicate_aliases.iter().cloned());

        for claim in claims {
            // Look up authoritative assertions matching this claim's tail path
            // Uses predicate aliases to enable semantic matching (e.g., enabled ↔ required)
            let auth_assertions = match index.lookup_with_aliases(
                &claim.concept_path,
                &claim.predicate,
                &all_aliases,
            ) {
                Some(assertions) => assertions,
                None => continue, // No authoritative coverage for this concept
            };

            // Find conflicting authoritative sources
            let mut conflicts = Vec::new();
            for assertion in auth_assertions {
                // Skip if it's the same assertion (same subject = same concept path)
                // This prevents a code claim from conflicting with itself.
                // NOTE: We do NOT skip based on SourceClass::Expert alone, because
                // Trust Pack assertions are also Expert tier but should be used as
                // authoritative sources for conflict detection.
                if assertion.subject == claim.concept_path {
                    continue;
                }

                // Auto-create alias if enabled (regardless of value conflict)
                // This bridges the code path to the authoritative path for future queries
                if config.aliases.auto_create_aliases {
                    if let Err(e) = self
                        .create_alias_if_new(
                            &claim.concept_path,
                            &assertion.subject,
                            agent_id,
                            timestamp,
                        )
                        .await
                    {
                        warn!(
                            code_path = %claim.concept_path,
                            auth_path = %assertion.subject,
                            error = %e,
                            "Failed to create alias"
                        );
                    } else {
                        aliases_created += 1;
                    }
                }

                // Check if value differs (for conflict reporting)
                if assertion.object != claim.value {
                    // Consider Tier 0-3 as authoritative (includes Expert/Policy assertions)
                    // This matches the behavior in ephemeral mode's check_conflicts_pure
                    if assertion.source_class.tier() <= 3 {
                        let rfc_citation = ConflictingSource::extract_citation(&assertion.subject);

                        // Look up policy source from pack source store
                        let pack_source_result =
                            self.pack_source_store.get_pack_source(&assertion.subject).await;

                        let policy_source = match &pack_source_result {
                            Ok(Some(info)) => {
                                debug!(
                                    subject = %assertion.subject,
                                    pack_name = %info.pack_name,
                                    "Found pack source for assertion"
                                );
                                Some(PolicySourceInfo {
                                    pack_name: info.pack_name.clone(),
                                    pack_version: info.pack_version.clone(),
                                    issuer_hex: info.issuer_hex.clone(),
                                    signer_name: info.signer_name.clone(),
                                    contact: info.contact.clone(),
                                })
                            }
                            Ok(None) => {
                                debug!(
                                    subject = %assertion.subject,
                                    "No pack source found for assertion"
                                );
                                None
                            }
                            Err(e) => {
                                warn!(
                                    subject = %assertion.subject,
                                    error = %e,
                                    "Error looking up pack source"
                                );
                                None
                            }
                        };

                        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);

            // Check if this concept has been acknowledged and parse expiry info
            let (acknowledged, ack_expired) = if let Some(ack) =
                ack_map.get(claim.concept_path.as_str())
            {
                // Format timestamp as human-readable
                let formatted_ts = format_timestamp(ack.timestamp);

                // Parse acknowledgment payload (JSON or legacy plain text)
                let (reason, expires_at, expired) = match &ack.object {
                    stemedb_core::types::ObjectValue::Text(s) => {
                        // Try to parse as JSON (new format with expiry support)
                        if let Ok(payload) = serde_json::from_str::<serde_json::Value>(s) {
                            let reason = payload
                                .get("reason")
                                .and_then(|v| v.as_str())
                                .unwrap_or("No reason provided")
                                .to_string();

                            // Parse expires_at once and derive both formatted string and expiry status
                            let expires_at_ts = payload.get("expires_at").and_then(|v| v.as_u64());
                            let expires_at = expires_at_ts.map(crate::expiry::format_expiry);
                            let expired =
                                expires_at_ts.map(crate::expiry::is_expired).unwrap_or(false);

                            (reason, expires_at, expired)
                        } else {
                            // Legacy format: plain text reason, no expiry
                            (s.clone(), None, false)
                        }
                    }
                    _ => ("No reason provided".to_string(), None, false),
                };

                (
                    Some(AcknowledgmentInfo {
                        timestamp: formatted_ts,
                        by: "aphoria".to_string(),
                        reason,
                        expires_at,
                        expired,
                    }),
                    expired,
                )
            } else {
                (None, false)
            };

            // Determine verdict:
            // - If acknowledged and NOT expired: Ack
            // - If acknowledged but EXPIRED: use normal threshold logic (resurface as Block/Flag)
            // - If not acknowledged: use normal threshold logic
            let verdict = if acknowledged.is_some() && !ack_expired {
                acked_count += 1;
                Verdict::Ack
            } else if conflict_score >= config.thresholds.block {
                Verdict::Block
            } else if conflict_score >= config.thresholds.flag {
                Verdict::Flag
            } else {
                Verdict::Pass
            };

            results.push(ConflictResult {
                claim: claim.clone(),
                conflicts,
                conflict_score,
                verdict,
                acknowledged,
                trace: None, // Persistent mode doesn't populate traces (for now)
            });
        }

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

        Ok(results)
    }

    /// Load an assertion from the store using its hash.
    pub async fn load_assertion_by_hash(&self, hash: &[u8; 32]) -> Option<Assertion> {
        let hash_hex = hex::encode(hash);
        let reverse_key = stemedb_storage::key_codec::hash_subject_key(&hash_hex);

        let subject = self.store.get(&reverse_key).await.ok().flatten().and_then(|bytes| {
            String::from_utf8(bytes)
                .map_err(|e| warn!(hash = %hash_hex, error = %e, "Invalid UTF-8 in reverse index"))
                .ok()
        })?;

        let assertion_key = stemedb_storage::key_codec::assertion_key(&subject, &hash_hex);
        self.store.get(&assertion_key).await.ok().flatten().and_then(|bytes| {
            stemedb_core::serde::deserialize::<Assertion>(&bytes)
                .map_err(|e| warn!(hash = %hash_hex, error = %e, "Failed to deserialize"))
                .ok()
        })
    }
}
