//! Ephemeral conflict detector that works entirely in-memory.
//!
//! This is the fast path for `aphoria scan` when persistence is not needed.
//! It builds the authoritative corpus and concept index once, then can check
//! conflicts against any number of claims without disk I/O.

use std::collections::HashMap;

use ed25519_dalek::SigningKey;
use stemedb_core::types::Assertion;
use tracing::{info, instrument, warn};

use crate::config::{AphoriaConfig, CorpusConfig};
use crate::corpus::CorpusRegistry;
use crate::policy::TrustPack;
use crate::types::{ConflictResult, ExtractedClaim, PolicySourceInfo, PredicateAliasSet};

use super::concept_index::ConceptIndex;
use super::conflict::check_conflicts_with_predicate_aliases;
use super::corpus::current_timestamp;

/// Ephemeral conflict detector that works entirely in-memory.
///
/// This is the fast path for `aphoria scan` when persistence is not needed.
/// It builds the authoritative corpus and concept index once, then can check
/// conflicts against any number of claims without disk I/O.
///
/// # Example
///
/// ```ignore
/// let detector = EphemeralDetector::new(&signing_key, &corpus_config);
/// let conflicts = detector.check_conflicts(&claims, &config);
/// ```
pub struct EphemeralDetector {
    /// Pre-built authoritative corpus (RFC, OWASP, vendor assertions).
    #[allow(dead_code)]
    corpus: Vec<Assertion>,
    /// In-memory index for tail-path matching.
    index: ConceptIndex,
    /// In-memory aliases from policies.
    aliases: HashMap<String, String>,
    /// Mapping from assertion subject to policy source info.
    /// Used to track which Trust Pack an assertion came from.
    pack_sources: HashMap<String, PolicySourceInfo>,
    /// Predicate aliases for semantic matching.
    predicate_aliases: Vec<PredicateAliasSet>,
}

impl EphemeralDetector {
    /// Create a new ephemeral detector with the full authoritative corpus.
    ///
    /// This builds the corpus from all configured sources (hardcoded, RFC, OWASP, vendor)
    /// using the CorpusRegistry. The corpus and index are built entirely in-memory.
    ///
    /// # Arguments
    ///
    /// * `signing_key` - Ed25519 key for signing assertions
    /// * `corpus_config` - Configuration for corpus sources
    #[instrument(skip(signing_key, corpus_config))]
    pub fn new(signing_key: &SigningKey, corpus_config: &CorpusConfig) -> Self {
        let registry = CorpusRegistry::with_defaults(corpus_config);
        let timestamp = current_timestamp();

        // Build the full corpus from registry (offline mode to avoid network I/O)
        let result = registry.build_all(signing_key, timestamp, corpus_config, true);

        let corpus = match result {
            Ok(build_result) => {
                info!(
                    total = build_result.total_assertions(),
                    successful = build_result.successful_builders(),
                    skipped = build_result.skipped_builders(),
                    "Corpus built from registry"
                );
                build_result.assertions
            }
            Err(e) => {
                warn!(error = %e, "Corpus build failed, using empty corpus");
                Vec::new()
            }
        };

        let index = ConceptIndex::build(&corpus);

        info!(
            corpus_size = corpus.len(),
            index_entries = index.entries.len(),
            "EphemeralDetector initialized"
        );

        Self {
            corpus,
            index,
            aliases: HashMap::new(),
            pack_sources: HashMap::new(),
            predicate_aliases: Vec::new(),
        }
    }

    /// Create a new ephemeral detector with just the hardcoded corpus.
    ///
    /// This is a faster initialization path that only uses the built-in assertions.
    /// Useful for testing or when minimal corpus is sufficient.
    #[allow(dead_code)]
    #[instrument(skip(signing_key))]
    pub fn new_minimal(signing_key: &SigningKey) -> Self {
        let corpus = super::create_authoritative_corpus(signing_key);
        let index = ConceptIndex::build(&corpus);

        info!(
            corpus_size = corpus.len(),
            index_entries = index.entries.len(),
            "EphemeralDetector initialized (minimal corpus)"
        );

        Self {
            corpus,
            index,
            aliases: HashMap::new(),
            pack_sources: HashMap::new(),
            predicate_aliases: Vec::new(),
        }
    }

    /// Ingest policies into the detector.
    ///
    /// Adds assertions from trust packs to the corpus/index and aliases to the alias map.
    /// Also tracks which pack each assertion came from for provenance reporting,
    /// and imports predicate aliases for semantic matching.
    pub fn ingest_policies(&mut self, policies: &[TrustPack]) {
        let mut new_assertions = 0;
        let mut new_aliases = 0;
        let mut new_predicate_aliases = 0;

        for pack in policies {
            // Create policy source info for this pack
            let policy_info = PolicySourceInfo {
                pack_name: pack.header.name.clone(),
                pack_version: pack.header.version.clone(),
                issuer_hex: hex::encode(&pack.header.issuer_id[..4]),
                signer_name: pack.header.signer_name.clone(),
                contact: pack.header.contact.clone(),
            };

            // Add assertions to corpus and index
            // Use predicate alias normalization when building keys
            for assertion in &pack.assertions {
                self.corpus.push(assertion.clone());
                // Normalize predicate using current predicate aliases
                let normalized_predicate = ConceptIndex::normalize_predicate(
                    &assertion.predicate,
                    &self.predicate_aliases,
                );
                // Add to index with normalized predicate
                if let Some(key) = ConceptIndex::make_key(&assertion.subject, normalized_predicate)
                {
                    self.index.entries.entry(key).or_default().push(assertion.clone());
                }
                // Track pack source for this assertion (keyed by subject)
                self.pack_sources.insert(assertion.subject.clone(), policy_info.clone());
                new_assertions += 1;
            }

            // Add concept aliases
            for alias in &pack.aliases {
                self.aliases.insert(alias.alias.to_string(), alias.canonical.to_string());
                new_aliases += 1;
            }

            // Add predicate aliases from pack
            for pack_alias in &pack.predicate_aliases {
                self.predicate_aliases.push(PredicateAliasSet::from(pack_alias));
                new_predicate_aliases += 1;
            }
        }

        info!(new_assertions, new_aliases, new_predicate_aliases, "Ingested policies");
    }

    /// Set predicate aliases from config.
    ///
    /// This allows predicate aliases to be configured in aphoria.toml
    /// in addition to or instead of importing them from Trust Packs.
    #[allow(dead_code)]
    pub fn set_predicate_aliases(&mut self, aliases: Vec<PredicateAliasSet>) {
        self.predicate_aliases = aliases;
    }

    /// Get the current predicate aliases.
    #[allow(dead_code)]
    pub fn predicate_aliases(&self) -> &[PredicateAliasSet] {
        &self.predicate_aliases
    }

    /// Get the policy source info for a given assertion subject.
    #[allow(dead_code)] // Used in tests
    pub fn get_pack_source(&self, subject: &str) -> Option<&PolicySourceInfo> {
        self.pack_sources.get(subject)
    }

    /// Check for conflicts between extracted claims and authoritative sources.
    ///
    /// This is a pure in-memory operation. No persistence, no aliases created.
    /// Uses both predicate aliases from config and those imported from Trust Packs.
    ///
    /// # Arguments
    ///
    /// * `claims` - Extracted claims from source code
    /// * `config` - Configuration with thresholds
    ///
    /// # Returns
    ///
    /// Vector of conflict results, with debug traces populated based on config.
    pub fn check_conflicts(
        &self,
        claims: &[ExtractedClaim],
        config: &AphoriaConfig,
    ) -> Vec<ConflictResult> {
        // Merge predicate aliases from config and from imported packs
        let mut all_aliases = config.predicate_aliases.to_alias_sets();
        all_aliases.extend(self.predicate_aliases.clone());

        check_conflicts_with_predicate_aliases(
            claims,
            &self.index,
            &self.aliases,
            &self.pack_sources,
            &all_aliases,
            config,
            false,
        )
    }

    /// Check for conflicts with debug traces enabled.
    ///
    /// Like `check_conflicts`, but populates `ConflictTrace` for each result.
    pub fn check_conflicts_debug(
        &self,
        claims: &[ExtractedClaim],
        config: &AphoriaConfig,
    ) -> Vec<ConflictResult> {
        // Merge predicate aliases from config and from imported packs
        let mut all_aliases = config.predicate_aliases.to_alias_sets();
        all_aliases.extend(self.predicate_aliases.clone());

        check_conflicts_with_predicate_aliases(
            claims,
            &self.index,
            &self.aliases,
            &self.pack_sources,
            &all_aliases,
            config,
            true,
        )
    }

    /// Get the number of authoritative assertions in the corpus.
    #[allow(dead_code)]
    pub fn corpus_size(&self) -> usize {
        self.corpus.len()
    }

    /// Get the number of indexed concept keys.
    #[allow(dead_code)]
    pub fn index_size(&self) -> usize {
        self.index.entries.len()
    }
}
