//! Authoritative corpus management for Aphoria.
//!
//! This module provides a unified interface for building and managing the authoritative
//! corpus that Aphoria uses to detect conflicts. The corpus consists of assertions from
//! multiple sources:
//!
//! - **RFC** (Tier 0): Normative statements from IETF RFCs
//! - **OWASP** (Tier 1): Recommendations from OWASP Cheat Sheets
//! - **Vendor** (Tier 2): Best practices from vendor documentation
//! - **Community** (Emergent): Patterns learned from scans across projects
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │                     aphoria corpus build                         │
//! │                                                                  │
//! │  ┌──────────────┐  ┌──────────────┐  ┌───────────────────────┐  │
//! │  │ RFC Ingester │  │ OWASP        │  │ Vendor Bootstrapper   │  │
//! │  │ (Tier 0)     │  │ Ingester     │  │ (Tier 2)              │  │
//! │  │              │  │ (Tier 1)     │  │                       │  │
//! │  └──────┬───────┘  └──────┬───────┘  └───────────┬───────────┘  │
//! │         │                 │                      │              │
//! │         └─────────────────┼──────────────────────┘              │
//! │                           ▼                                     │
//! │                  ┌─────────────────┐                            │
//! │                  │ CorpusRegistry  │                            │
//! │                  └────────┬────────┘                            │
//! │                           ▼                                     │
//! │                  ┌─────────────────┐                            │
//! │                  │ Vec<Assertion>  │                            │
//! │                  └─────────────────┘                            │
//! └─────────────────────────────────────────────────────────────────┘
//! ```

mod authority_parser;
mod cli_created;
mod community;
mod enricher;
mod owasp;
mod resolver;
mod rfc;
mod subject_builder;
pub mod thresholds; // Public to allow config types to use ScaleAdaptiveThresholds
mod vendor;
mod wiki_corpus_builder;
mod wiki_importer;

pub use authority_parser::{parse_authority, Authority};
pub use cli_created::CliCreatedBuilder;
pub use community::{CommunityCorpusBuilder, PatternAggregateStore, StubPatternStore};
pub use enricher::{Enrichment, PatternEnricher};
pub use owasp::OwaspCorpusBuilder;
pub use resolver::CorpusResolver;
pub use rfc::RfcCorpusBuilder;
pub use subject_builder::build_corpus_subject;
pub use thresholds::{
    CorpusPromotionThresholds, PromotionCriteria, PromotionDecision, ScaleAdaptiveThresholds,
    ScaleTier,
};
pub use vendor::VendorCorpusBuilder;
pub use wiki_corpus_builder::promote_wiki_patterns_to_corpus;
pub use wiki_importer::{import_from_wiki, WikiParser, WikiPattern};

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

use crate::config::CorpusConfig;
use crate::AphoriaError;

/// A builder that produces authoritative assertions from a specific source.
///
/// Each corpus builder is responsible for:
/// 1. Fetching or loading source material (RFCs, OWASP docs, vendor docs)
/// 2. Parsing relevant claims from that material
/// 3. Converting claims to signed Episteme assertions
pub trait CorpusBuilder: Send + Sync {
    /// Human-readable name for this corpus source.
    fn name(&self) -> &str;

    /// URI scheme used by this corpus (e.g., "rfc", "owasp", "vendor").
    fn scheme(&self) -> &str;

    /// Default source tier for assertions from this corpus.
    ///
    /// - Tier 0: Regulatory (RFCs with MUST/SHALL)
    /// - Tier 1: Clinical (OWASP, security best practices)
    /// - Tier 2: Observational (Vendor documentation)
    fn default_tier(&self) -> u8;

    /// Build assertions from this corpus source.
    ///
    /// # Arguments
    ///
    /// * `signing_key` - Ed25519 key for signing assertions
    /// * `timestamp` - Unix timestamp for assertion creation
    /// * `config` - Corpus configuration (cache paths, options)
    ///
    /// # Returns
    ///
    /// A vector of signed assertions, or an error if building fails.
    fn build(
        &self,
        signing_key: &SigningKey,
        timestamp: u64,
        config: &CorpusConfig,
    ) -> Result<Vec<Assertion>, AphoriaError>;

    /// Whether this builder requires network access.
    fn requires_network(&self) -> bool {
        false
    }

    /// List of source identifiers this builder will fetch.
    ///
    /// For RFC builder, this might be `["7519", "6749", "8446"]`.
    /// For OWASP builder, this might be `["Authentication", "JWT", "TLS"]`.
    fn source_ids(&self) -> Vec<String> {
        vec![]
    }
}

/// Async variant of CorpusBuilder for builders that need async operations.
///
/// Use this trait for builders that query databases or make network calls.
/// Synchronous builders should continue using `CorpusBuilder`.
#[async_trait::async_trait]
pub trait AsyncCorpusBuilder: Send + Sync {
    /// Human-readable name for this corpus source.
    fn name(&self) -> &str;

    /// URI scheme used by this corpus (e.g., "community").
    fn scheme(&self) -> &str;

    /// Default source tier for assertions from this corpus.
    fn default_tier(&self) -> u8;

    /// Build assertions from this corpus source (async).
    async fn build(
        &self,
        signing_key: &SigningKey,
        timestamp: u64,
        config: &CorpusConfig,
    ) -> Result<Vec<Assertion>, AphoriaError>;

    /// Whether this builder requires network access.
    fn requires_network(&self) -> bool {
        false
    }

    /// List of source identifiers this builder will fetch.
    fn source_ids(&self) -> Vec<String> {
        vec![]
    }
}

/// Registry for managing multiple corpus builders.
///
/// The registry handles:
/// - Builder registration and lookup
/// - Coordinated corpus building across all sources
/// - Filtering by source type (--only flag)
pub struct CorpusRegistry {
    sync_builders: Vec<Box<dyn CorpusBuilder>>,
    async_builders: Vec<Box<dyn AsyncCorpusBuilder>>,
}

impl CorpusRegistry {
    /// Create a new empty registry.
    pub fn new() -> Self {
        Self { sync_builders: Vec::new(), async_builders: Vec::new() }
    }

    /// Create a registry with default builders (RFC, OWASP, Vendor).
    ///
    /// This does NOT include the community corpus builder, as it requires
    /// StemeDB stores. Use `with_stores()` instead if you need community corpus.
    pub fn with_defaults(config: &CorpusConfig) -> Self {
        let mut registry = Self::new();

        if config.include_rfc {
            registry.register(Box::new(RfcCorpusBuilder::new(&config.rfc_list)));
        }

        if config.include_owasp {
            registry.register(Box::new(OwaspCorpusBuilder::new()));
        }

        if config.include_vendor {
            registry.register(Box::new(VendorCorpusBuilder::new()));
        }

        registry
    }

    /// Create a registry with all builders including community corpus (requires stores).
    ///
    /// Use this constructor when you have access to StemeDB stores (LocalEpisteme).
    /// The community corpus builder queries pattern aggregates from storage.
    ///
    /// # Arguments
    ///
    /// * `config` - Corpus configuration
    /// * `kv_store` - Project KV store for community patterns
    /// * `predicate_index` - Predicate index for community patterns
    /// * `corpus_store` - Optional corpus database store for CLI-created items
    pub fn with_stores(
        config: &CorpusConfig,
        kv_store: std::sync::Arc<stemedb_storage::HybridStore>,
        predicate_index: std::sync::Arc<
            stemedb_storage::GenericPredicateIndexStore<
                std::sync::Arc<stemedb_storage::HybridStore>,
            >,
        >,
        corpus_store: Option<std::sync::Arc<stemedb_storage::HybridStore>>,
    ) -> Self {
        let mut registry = Self::with_defaults(config);

        // Add community corpus builder if enabled
        if config.use_community {
            let community_builder =
                CommunityCorpusBuilder::from_stores(kv_store, predicate_index, config);
            registry.register_async(Box::new(community_builder));
            info!("Registered community corpus builder (async)");
        }

        // Add CLI-created items builder if corpus store is available
        if let Some(corpus_store) = corpus_store {
            registry.register_async(Box::new(CliCreatedBuilder::new(corpus_store)));
            info!("Registered CLI-created items corpus builder (async)");
        }

        registry
    }

    /// Register a synchronous corpus builder.
    pub fn register(&mut self, builder: Box<dyn CorpusBuilder>) {
        self.sync_builders.push(builder);
    }

    /// Register an asynchronous corpus builder.
    pub fn register_async(&mut self, builder: Box<dyn AsyncCorpusBuilder>) {
        self.async_builders.push(builder);
    }

    /// Get registered builder names.
    pub fn builder_names(&self) -> Vec<&str> {
        let mut names: Vec<&str> = self.sync_builders.iter().map(|b| b.name()).collect();
        names.extend(self.async_builders.iter().map(|b| b.name()));
        names
    }

    /// Get builder info for listing.
    pub fn list_builders(&self) -> Vec<CorpusBuilderInfo> {
        let mut infos: Vec<CorpusBuilderInfo> = self
            .sync_builders
            .iter()
            .map(|b| CorpusBuilderInfo {
                name: b.name().to_string(),
                scheme: b.scheme().to_string(),
                tier: b.default_tier(),
                requires_network: b.requires_network(),
                source_ids: b.source_ids(),
            })
            .collect();

        infos.extend(self.async_builders.iter().map(|b| CorpusBuilderInfo {
            name: b.name().to_string(),
            scheme: b.scheme().to_string(),
            tier: b.default_tier(),
            requires_network: b.requires_network(),
            source_ids: b.source_ids(),
        }));

        infos
    }

    /// Build assertions from all registered corpus sources.
    ///
    /// # Arguments
    ///
    /// * `signing_key` - Ed25519 key for signing assertions
    /// * `timestamp` - Unix timestamp for assertion creation
    /// * `config` - Corpus configuration
    /// * `offline` - If true, skip builders that require network access
    ///
    /// # Returns
    ///
    /// A combined vector of assertions from all sources, along with build statistics.
    #[instrument(skip(self, signing_key, config), fields(builders = self.sync_builders.len() + self.async_builders.len()))]
    pub async fn build_all(
        &self,
        signing_key: &SigningKey,
        timestamp: u64,
        config: &CorpusConfig,
        offline: bool,
    ) -> Result<CorpusBuildResult, AphoriaError> {
        let mut all_assertions = Vec::new();
        let mut stats = Vec::new();

        // Build from sync builders
        for builder in &self.sync_builders {
            // Skip network-requiring builders in offline mode
            if offline && builder.requires_network() {
                info!(builder = builder.name(), "Skipping (offline mode)");
                stats.push(CorpusBuilderStats {
                    name: builder.name().to_string(),
                    scheme: builder.scheme().to_string(),
                    assertions_built: 0,
                    skipped: true,
                    error: None,
                });
                continue;
            }

            info!(builder = builder.name(), scheme = builder.scheme(), "Building corpus");

            match builder.build(signing_key, timestamp, config) {
                Ok(assertions) => {
                    let count = assertions.len();
                    info!(builder = builder.name(), assertions = count, "Corpus built");
                    stats.push(CorpusBuilderStats {
                        name: builder.name().to_string(),
                        scheme: builder.scheme().to_string(),
                        assertions_built: count,
                        skipped: false,
                        error: None,
                    });
                    all_assertions.extend(assertions);
                }
                Err(e) => {
                    tracing::warn!(builder = builder.name(), error = %e, "Corpus build failed");
                    stats.push(CorpusBuilderStats {
                        name: builder.name().to_string(),
                        scheme: builder.scheme().to_string(),
                        assertions_built: 0,
                        skipped: false,
                        error: Some(e.to_string()),
                    });
                    // Continue with other builders even if one fails
                }
            }
        }

        // Build from async builders
        for builder in &self.async_builders {
            if offline && builder.requires_network() {
                info!(builder = builder.name(), "Skipping (offline mode)");
                stats.push(CorpusBuilderStats {
                    name: builder.name().to_string(),
                    scheme: builder.scheme().to_string(),
                    assertions_built: 0,
                    skipped: true,
                    error: None,
                });
                continue;
            }

            info!(builder = builder.name(), scheme = builder.scheme(), "Building corpus (async)");

            match builder.build(signing_key, timestamp, config).await {
                Ok(assertions) => {
                    let count = assertions.len();
                    info!(builder = builder.name(), assertions = count, "Corpus built");
                    stats.push(CorpusBuilderStats {
                        name: builder.name().to_string(),
                        scheme: builder.scheme().to_string(),
                        assertions_built: count,
                        skipped: false,
                        error: None,
                    });
                    all_assertions.extend(assertions);
                }
                Err(e) => {
                    tracing::warn!(builder = builder.name(), error = %e, "Corpus build failed");
                    stats.push(CorpusBuilderStats {
                        name: builder.name().to_string(),
                        scheme: builder.scheme().to_string(),
                        assertions_built: 0,
                        skipped: false,
                        error: Some(e.to_string()),
                    });
                }
            }
        }

        Ok(CorpusBuildResult { assertions: all_assertions, stats })
    }
}

impl Default for CorpusRegistry {
    fn default() -> Self {
        Self::new()
    }
}

/// Information about a corpus builder.
#[derive(Debug, Clone)]
pub struct CorpusBuilderInfo {
    /// Human-readable name.
    pub name: String,
    /// URI scheme.
    pub scheme: String,
    /// Default source tier.
    pub tier: u8,
    /// Whether network access is required.
    pub requires_network: bool,
    /// Source identifiers (RFC numbers, cheat sheet names, etc.).
    pub source_ids: Vec<String>,
}

/// Statistics for a single corpus builder.
#[derive(Debug, Clone)]
pub struct CorpusBuilderStats {
    /// Builder name.
    pub name: String,
    /// URI scheme.
    pub scheme: String,
    /// Number of assertions built.
    pub assertions_built: usize,
    /// Whether the builder was skipped (e.g., offline mode).
    pub skipped: bool,
    /// Error message if build failed.
    pub error: Option<String>,
}

/// Result of building the full corpus.
#[derive(Debug)]
pub struct CorpusBuildResult {
    /// All assertions from all builders.
    pub assertions: Vec<Assertion>,
    /// Per-builder statistics.
    pub stats: Vec<CorpusBuilderStats>,
}

impl CorpusBuildResult {
    /// Get total assertion count.
    pub fn total_assertions(&self) -> usize {
        self.assertions.len()
    }

    /// Get count of successful builders.
    pub fn successful_builders(&self) -> usize {
        self.stats.iter().filter(|s| s.error.is_none() && !s.skipped).count()
    }

    /// Get count of failed builders.
    pub fn failed_builders(&self) -> usize {
        self.stats.iter().filter(|s| s.error.is_some()).count()
    }

    /// Get count of skipped builders.
    pub fn skipped_builders(&self) -> usize {
        self.stats.iter().filter(|s| s.skipped).count()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::bridge::generate_signing_key;

    #[test]
    fn test_registry_default_empty() {
        let registry = CorpusRegistry::new();
        assert!(registry.builder_names().is_empty());
    }

    #[test]
    fn test_registry_with_defaults() {
        let config = CorpusConfig::default();
        let registry = CorpusRegistry::with_defaults(&config);

        // Should have all three default builders
        let names = registry.builder_names();
        assert!(names.contains(&"RFC"));
        assert!(names.contains(&"OWASP"));
        assert!(names.contains(&"VendorDocs"));
    }

    #[test]
    fn test_registry_selective_builders() {
        let config =
            CorpusConfig { include_rfc: false, include_owasp: false, ..Default::default() };

        let registry = CorpusRegistry::with_defaults(&config);
        let names = registry.builder_names();

        assert!(names.contains(&"VendorDocs"));
        assert!(!names.contains(&"RFC"));
        assert!(!names.contains(&"OWASP"));
    }

    #[tokio::test]
    async fn test_build_all_offline() {
        let config = CorpusConfig::default();
        let registry = CorpusRegistry::with_defaults(&config);
        let key = generate_signing_key();
        let timestamp = 1706832000;

        let result = registry.build_all(&key, timestamp, &config, true).await.expect("build_all");

        // In offline mode, network-requiring builders should be skipped
        // but vendor should still work
        assert!(result.total_assertions() > 0);
        // In offline mode some builders may be skipped - this is expected behavior
    }

    #[test]
    fn test_corpus_builder_info() {
        let config = CorpusConfig::default();
        let registry = CorpusRegistry::with_defaults(&config);
        let infos = registry.list_builders();

        for info in &infos {
            assert!(!info.name.is_empty());
            assert!(!info.scheme.is_empty());
            assert!(info.tier <= 3);
        }
    }
}
