//! 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:
//!
//! - **Hardcoded** (Tier 0-1): Built-in RFC/OWASP assertions for common security patterns
//! - **RFC** (Tier 0): Normative statements from IETF RFCs
//! - **OWASP** (Tier 1): Recommendations from OWASP Cheat Sheets
//! - **Vendor** (Tier 2): Best practices from vendor documentation
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │                     aphoria corpus build                         │
//! │                                                                  │
//! │  ┌──────────────┐  ┌──────────────┐  ┌───────────────────────┐  │
//! │  │ RFC Ingester │  │ OWASP        │  │ Vendor Bootstrapper   │  │
//! │  │ (Tier 0)     │  │ Ingester     │  │ (Tier 2)              │  │
//! │  │              │  │ (Tier 1)     │  │                       │  │
//! │  └──────┬───────┘  └──────┬───────┘  └───────────┬───────────┘  │
//! │         │                 │                      │              │
//! │         └─────────────────┼──────────────────────┘              │
//! │                           ▼                                     │
//! │                  ┌─────────────────┐                            │
//! │                  │ CorpusRegistry  │                            │
//! │                  └────────┬────────┘                            │
//! │                           ▼                                     │
//! │                  ┌─────────────────┐                            │
//! │                  │ Vec<Assertion>  │                            │
//! │                  └─────────────────┘                            │
//! └─────────────────────────────────────────────────────────────────┘
//! ```

mod hardcoded;
mod owasp;
mod rfc;
mod vendor;

pub use hardcoded::HardcodedCorpusBuilder;
pub use owasp::OwaspCorpusBuilder;
pub use rfc::RfcCorpusBuilder;
pub use vendor::VendorCorpusBuilder;

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![]
    }
}

/// 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 {
    builders: Vec<Box<dyn CorpusBuilder>>,
}

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

    /// Create a registry with default builders.
    pub fn with_defaults(config: &CorpusConfig) -> Self {
        let mut registry = Self::new();

        if config.include_hardcoded {
            registry.register(Box::new(HardcodedCorpusBuilder::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
    }

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

    /// Get registered builder names.
    pub fn builder_names(&self) -> Vec<&str> {
        self.builders.iter().map(|b| b.name()).collect()
    }

    /// Get builder info for listing.
    pub fn list_builders(&self) -> Vec<CorpusBuilderInfo> {
        self.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()
    }

    /// 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.builders.len()))]
    pub 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();

        for builder in &self.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
                }
            }
        }

        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 four default builders
        let names = registry.builder_names();
        assert!(names.contains(&"Hardcoded"));
        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(&"Hardcoded"));
        assert!(names.contains(&"VendorDocs"));
        assert!(!names.contains(&"RFC"));
        assert!(!names.contains(&"OWASP"));
    }

    #[test]
    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).expect("build_all");

        // In offline mode, network-requiring builders should be skipped
        // but hardcoded and 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);
        }
    }
}
