//! Community corpus builder from emergent patterns.
//!
//! This builder promotes patterns based on community adoption rates:
//! - 95%+ adoption + RFC match → Tier 0 (Regulatory)
//! - 80%+ adoption + OWASP match → Tier 1 (Clinical)
//! - 50%+ adoption → Tier 2 (Emerging, requires review)

use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;

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

use super::thresholds::{
    CorpusPromotionThresholds, PromotionDecision, ScaleAdaptiveThresholds, ScaleTier,
};
use crate::community::PatternAggregate;
use crate::config::CorpusConfig;
use crate::episteme::create_authoritative_assertion;
use crate::AphoriaError;

/// Trait for querying pattern aggregates from storage.
///
/// In shadow mode (Week 1-2), this is a stub that returns empty results.
/// In future weeks, this will query actual pattern data.
pub trait PatternAggregateStore: Send + Sync {
    /// Get popular patterns with at least min_projects reporting them.
    ///
    /// Returns up to `limit` patterns sorted by project_count descending.
    fn get_popular_patterns(
        &self,
        min_projects: u64,
        limit: usize,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<PatternAggregate>, AphoriaError>> + Send + '_>>;

    /// Get total number of unique projects scanned.
    fn get_total_projects(
        &self,
    ) -> Pin<Box<dyn Future<Output = Result<u64, AphoriaError>> + Send + '_>>;
}

/// Stub implementation for shadow mode.
///
/// Returns empty results - actual storage integration comes later.
pub struct StubPatternStore;

impl PatternAggregateStore for StubPatternStore {
    fn get_popular_patterns(
        &self,
        _min_projects: u64,
        _limit: usize,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<PatternAggregate>, AphoriaError>> + Send + '_>>
    {
        Box::pin(async move { Ok(vec![]) })
    }

    fn get_total_projects(
        &self,
    ) -> Pin<Box<dyn Future<Output = Result<u64, AphoriaError>> + Send + '_>> {
        Box::pin(async move { Ok(0) })
    }
}

/// Community corpus builder from aggregated patterns.
///
/// Reads pattern aggregates from the community server or local cache
/// and promotes high-confidence patterns to the corpus based on:
/// - Adoption rate across projects
/// - Authority source matching (RFC, OWASP, etc.)
/// - Manual promotion overrides
pub struct CommunityCorpusBuilder {
    /// Pattern aggregate store for querying community data.
    pattern_store: Box<dyn PatternAggregateStore>,

    /// Legacy promotion thresholds (used when use_adaptive=false).
    thresholds: CorpusPromotionThresholds,

    /// Scale-adaptive thresholds (used when use_adaptive=true).
    adaptive_thresholds: ScaleAdaptiveThresholds,

    /// Whether to use adaptive thresholds (default: true).
    use_adaptive: bool,

    /// Path to manually promoted patterns file.
    ///
    /// Format: `.aphoria/corpus/community.toml`
    manual_promotions_path: Option<PathBuf>,
}

impl CommunityCorpusBuilder {
    /// Create a new community corpus builder.
    ///
    /// # Arguments
    ///
    /// * `pattern_store` - Store for querying pattern aggregates
    /// * `thresholds` - Promotion thresholds for tier decisions
    pub fn new(
        pattern_store: Box<dyn PatternAggregateStore>,
        thresholds: CorpusPromotionThresholds,
    ) -> Self {
        Self {
            pattern_store,
            thresholds,
            adaptive_thresholds: ScaleAdaptiveThresholds::default(),
            use_adaptive: false, // Legacy constructor defaults to legacy behavior
            manual_promotions_path: None,
        }
    }

    /// Create a builder with stub storage (for testing/shadow mode).
    pub fn with_stub_storage(thresholds: CorpusPromotionThresholds) -> Self {
        Self::new(Box::new(StubPatternStore), thresholds)
    }

    /// Create a builder from StemeDB stores with configuration.
    ///
    /// This is the production constructor that uses real storage and respects config.
    pub fn from_stores(
        kv_store: std::sync::Arc<stemedb_storage::HybridStore>,
        predicate_index: std::sync::Arc<
            stemedb_storage::GenericPredicateIndexStore<
                std::sync::Arc<stemedb_storage::HybridStore>,
            >,
        >,
        config: &CorpusConfig,
    ) -> Self {
        use crate::community::StemeDBPatternStore;
        let pattern_store = Box::new(StemeDBPatternStore::new(kv_store, predicate_index));

        let adaptive_thresholds = config.adaptive_thresholds.clone().unwrap_or_default();

        Self {
            pattern_store,
            thresholds: CorpusPromotionThresholds::default(), // Keep for legacy path
            adaptive_thresholds,
            use_adaptive: !config.use_legacy_thresholds,
            manual_promotions_path: None,
        }
    }

    /// Set path to manual promotions file.
    pub fn with_manual_promotions(mut self, path: PathBuf) -> Self {
        self.manual_promotions_path = Some(path);
        self
    }

    /// Calculate adoption rate for a pattern.
    #[allow(dead_code)]
    async fn calculate_adoption_rate(
        &self,
        pattern: &PatternAggregate,
    ) -> Result<f64, AphoriaError> {
        let total_projects = self.pattern_store.get_total_projects().await?;

        if total_projects == 0 {
            return Ok(0.0);
        }

        Ok(pattern.project_count as f64 / total_projects as f64)
    }

    /// Check if pattern matches an authority source.
    ///
    /// Compares pattern subject against hardcoded corpus using tail-path matching.
    /// Returns (has_match, authority_scheme).
    fn check_authority_match(&self, _pattern: &PatternAggregate) -> (bool, Option<String>) {
        // TODO: Implement tail-path matching against hardcoded corpus
        // For now, return no match (shadow mode)
        (false, None)
    }

    /// Decide if pattern should be promoted.
    fn should_promote(
        &self,
        pattern: &PatternAggregate,
        total_projects: u64,
        authority_match: (bool, Option<String>),
    ) -> PromotionDecision {
        if self.use_adaptive {
            self.adaptive_thresholds.evaluate(
                pattern.project_count,
                total_projects,
                authority_match.0,
                authority_match.1.as_deref(),
            )
        } else {
            // Legacy path for backward compatibility
            self.thresholds.evaluate(
                pattern.project_count,
                total_projects,
                authority_match.0,
                authority_match.1.as_deref(),
            )
        }
    }

    /// Create assertion from promoted pattern.
    fn create_assertion(
        &self,
        pattern: &PatternAggregate,
        adoption_rate: f64,
        authority_match: (bool, Option<String>),
        source_class: SourceClass,
        signing_key: &SigningKey,
        timestamp: u64,
    ) -> Result<Assertion, AphoriaError> {
        let description = self.format_description(pattern, adoption_rate, &authority_match);

        let object_value: ObjectValue = pattern.value.clone().into();

        Ok(create_authoritative_assertion(
            signing_key,
            &pattern.subject,
            &pattern.predicate,
            object_value,
            source_class,
            &description,
            timestamp,
        ))
    }

    /// Format description for promoted pattern.
    fn format_description(
        &self,
        pattern: &PatternAggregate,
        adoption_rate: f64,
        authority_match: &(bool, Option<String>),
    ) -> String {
        let mut parts = vec![format!(
            "Community: {:.0}% adoption ({} projects)",
            adoption_rate * 100.0,
            pattern.project_count
        )];

        if let (true, Some(scheme)) = authority_match {
            parts.push(format!("Authority: {}", scheme));
        }

        parts.join(", ")
    }

    /// Load manually promoted patterns from file.
    ///
    /// Returns empty vec in shadow mode (file doesn't exist yet).
    #[allow(dead_code)]
    fn load_promoted_patterns(
        &self,
        _signing_key: &SigningKey,
        _timestamp: u64,
    ) -> Result<Vec<Assertion>, AphoriaError> {
        // TODO: Load from .aphoria/corpus/community.toml
        // For now, return empty (shadow mode)
        Ok(vec![])
    }

    /// Build corpus in shadow mode (dry-run, logging only).
    ///
    /// Returns what WOULD be promoted without actually promoting.
    #[allow(dead_code)]
    #[instrument(skip(self, _signing_key), fields(builder = "Community"))]
    async fn build_shadow(
        &self,
        _signing_key: &SigningKey,
        _timestamp: u64,
    ) -> Result<Vec<PromotionCandidate>, AphoriaError> {
        info!("Shadow mode: Evaluating patterns for promotion");

        let total_projects = self.pattern_store.get_total_projects().await?;

        let patterns = self
            .pattern_store
            .get_popular_patterns(self.thresholds.emerging.min_projects, 1000)
            .await?;

        if patterns.is_empty() {
            info!("Shadow mode: No patterns found (stub storage)");
            return Ok(vec![]);
        }

        let mut candidates = Vec::new();

        for pattern in patterns {
            let adoption_rate = self.calculate_adoption_rate(&pattern).await?;
            let authority_match = self.check_authority_match(&pattern);
            let decision = self.should_promote(&pattern, total_projects, authority_match.clone());

            match decision {
                PromotionDecision::AutoPromote(source_class) => {
                    candidates.push(PromotionCandidate {
                        pattern,
                        adoption_rate,
                        authority_match,
                        decision,
                        source_class: Some(source_class),
                    });
                }
                PromotionDecision::RequireReview => {
                    candidates.push(PromotionCandidate {
                        pattern,
                        adoption_rate,
                        authority_match,
                        decision,
                        source_class: None,
                    });
                }
                _ => {
                    // Skip or SuggestOnly - not candidates
                }
            }
        }

        // Log shadow results
        if !candidates.is_empty() {
            info!("Shadow mode: Would have promoted {} patterns", candidates.len());
            for candidate in &candidates {
                let authority_str = if let (true, Some(scheme)) = &candidate.authority_match {
                    format!(", {} match", scheme)
                } else {
                    String::new()
                };

                let tier_str = match candidate.source_class {
                    Some(SourceClass::Regulatory) => "Tier 0",
                    Some(SourceClass::Clinical) => "Tier 1",
                    _ => "Tier 2 (review required)",
                };

                info!(
                    "  - {}:{}={:?} ({:.0}% adoption{})",
                    candidate.pattern.subject,
                    candidate.pattern.predicate,
                    candidate.pattern.value,
                    candidate.adoption_rate * 100.0,
                    authority_str
                );
                info!("    → {}", tier_str);
            }
        }

        Ok(candidates)
    }
}

#[async_trait::async_trait]
impl super::AsyncCorpusBuilder for CommunityCorpusBuilder {
    fn name(&self) -> &str {
        "Community"
    }

    fn scheme(&self) -> &str {
        "code://"
    }

    fn default_tier(&self) -> u8 {
        2 // Observational (default for non-promoted patterns)
    }

    #[instrument(skip(self, signing_key, _config), fields(builder = "Community"))]
    async fn build(
        &self,
        signing_key: &SigningKey,
        timestamp: u64,
        _config: &CorpusConfig,
    ) -> Result<Vec<Assertion>, AphoriaError> {
        let total_projects = self.pattern_store.get_total_projects().await?;
        let scale_tier = ScaleTier::from_total_projects(total_projects);

        info!(
            total_projects,
            ?scale_tier,
            use_adaptive = self.use_adaptive,
            "Building community corpus with scale-adaptive thresholds"
        );

        // Determine minimum project threshold for initial query
        let min_projects_for_query = if self.use_adaptive {
            // Use micro tier's emerging floor as minimum (most permissive)
            2
        } else {
            self.thresholds.emerging.min_projects
        };

        // Fetch popular patterns (now properly async without block_on!)
        let patterns =
            self.pattern_store.get_popular_patterns(min_projects_for_query, 1000).await?;

        if patterns.is_empty() {
            info!("No patterns found for community corpus (empty store or below threshold)");
            return Ok(vec![]);
        }

        info!(pattern_count = patterns.len(), total_projects, "Evaluating patterns for promotion");

        let mut assertions = Vec::new();

        for pattern in patterns {
            let adoption_rate = if total_projects > 0 {
                pattern.project_count as f64 / total_projects as f64
            } else {
                0.0
            };

            let authority_match = self.check_authority_match(&pattern);
            let decision = self.should_promote(&pattern, total_projects, authority_match.clone());

            match decision {
                super::thresholds::PromotionDecision::AutoPromote(source_class) => {
                    info!(
                        subject = %pattern.subject,
                        predicate = %pattern.predicate,
                        adoption = %format!("{:.1}%", adoption_rate * 100.0),
                        tier = ?source_class,
                        "Auto-promoting pattern to corpus"
                    );

                    let assertion = self.create_assertion(
                        &pattern,
                        adoption_rate,
                        authority_match,
                        source_class,
                        signing_key,
                        timestamp,
                    )?;
                    assertions.push(assertion);
                }
                super::thresholds::PromotionDecision::RequireReview => {
                    info!(
                        subject = %pattern.subject,
                        predicate = %pattern.predicate,
                        adoption = %format!("{:.1}%", adoption_rate * 100.0),
                        "Pattern requires manual review (use aphoria-corpus-curator skill)"
                    );
                }
                _ => {
                    // Skip or SuggestOnly - not promoted
                }
            }
        }

        info!(promoted_count = assertions.len(), "Community corpus build complete");
        Ok(assertions)
    }
}

/// Promotion candidate for shadow mode logging.
#[derive(Debug)]
#[allow(dead_code)]
struct PromotionCandidate {
    pattern: PatternAggregate,
    adoption_rate: f64,
    authority_match: (bool, Option<String>),
    decision: PromotionDecision,
    source_class: Option<SourceClass>,
}

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

    #[test]
    fn test_community_builder_metadata() {
        let thresholds = CorpusPromotionThresholds::default();
        let builder = CommunityCorpusBuilder::with_stub_storage(thresholds);

        assert_eq!(builder.name(), "Community");
        assert_eq!(builder.scheme(), "code://");
        assert_eq!(builder.default_tier(), 2);
    }

    #[tokio::test]
    async fn test_build_returns_empty_in_shadow_mode() {
        let thresholds = CorpusPromotionThresholds::default();
        let builder = CommunityCorpusBuilder::with_stub_storage(thresholds);

        let key = crate::bridge::generate_signing_key();
        let config = CorpusConfig::default();

        let assertions = builder.build(&key, 1706832000, &config).await.expect("build");

        // Shadow mode: returns empty
        assert_eq!(assertions.len(), 0);
    }

    #[tokio::test]
    async fn test_stub_store() {
        let store = StubPatternStore;

        let patterns = store.get_popular_patterns(100, 10).await.expect("get_popular");
        assert_eq!(patterns.len(), 0);

        let total = store.get_total_projects().await.expect("get_total");
        assert_eq!(total, 0);
    }

    #[tokio::test]
    async fn test_calculate_adoption_rate_zero_projects() {
        let thresholds = CorpusPromotionThresholds::default();
        let builder = CommunityCorpusBuilder::with_stub_storage(thresholds);

        use crate::community::CommunityObjectValue;
        let pattern = PatternAggregate::new(
            "code://rust/*/tls/cert".to_string(),
            "enabled".to_string(),
            CommunityObjectValue::Boolean(true),
            1706832000,
        );

        let rate = builder.calculate_adoption_rate(&pattern).await.expect("calculate");

        // Zero projects → zero rate
        assert_eq!(rate, 0.0);
    }

    #[test]
    fn test_format_description() {
        let thresholds = CorpusPromotionThresholds::default();
        let builder = CommunityCorpusBuilder::with_stub_storage(thresholds);

        use crate::community::CommunityObjectValue;
        let pattern = PatternAggregate {
            subject: "code://rust/*/tls/cert".to_string(),
            predicate: "enabled".to_string(),
            value: CommunityObjectValue::Boolean(true),
            project_count: 850,
            observation_count: 1200,
            first_seen: 1000,
            last_seen: 2000,
        };

        // Without authority match
        let desc = builder.format_description(&pattern, 0.95, &(false, None));
        assert!(desc.contains("95% adoption"));
        assert!(desc.contains("850 projects"));
        assert!(!desc.contains("Authority"));

        // With authority match
        let desc =
            builder.format_description(&pattern, 0.95, &(true, Some("rfc://5246".to_string())));
        assert!(desc.contains("95% adoption"));
        assert!(desc.contains("Authority: rfc://5246"));
    }
}
