//! Corpus promotion thresholds for multi-tier decision making.
//!
//! This module defines criteria for promoting patterns from raw observations
//! to the community corpus at different authority tiers.

use serde::{Deserialize, Serialize};
use stemedb_core::types::SourceClass;

/// Criteria for promoting a pattern to a specific tier.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromotionCriteria {
    /// Minimum number of distinct projects that must report this pattern.
    pub min_projects: u64,

    /// Minimum adoption rate (0.0 to 1.0).
    ///
    /// Example: 0.95 means 95% of projects must have this pattern.
    pub min_adoption_rate: f64,

    /// Whether an authority source match is required.
    ///
    /// When true, pattern must match against RFC/OWASP/NIST assertions.
    pub require_authority: bool,

    /// Required authority source schemes (e.g., ["rfc://", "nist://"]).
    ///
    /// Only checked when `require_authority` is true.
    pub authority_sources: Vec<String>,

    /// Whether to automatically promote patterns meeting these criteria.
    ///
    /// When false, patterns require manual review via aphoria-corpus-curator skill.
    pub auto_promote: bool,

    /// Whether manual review is required (even if auto_promote is false).
    pub require_review: bool,
}

impl Default for PromotionCriteria {
    fn default() -> Self {
        Self {
            min_projects: 1,
            min_adoption_rate: 0.0,
            require_authority: false,
            authority_sources: vec![],
            auto_promote: false,
            require_review: false,
        }
    }
}

/// Multi-tier promotion thresholds for corpus building.
///
/// Patterns are evaluated against multiple tiers in priority order:
/// 1. Regulatory (Tier 0): 95%+ adoption, RFC-backed → AUTO-PROMOTE
/// 2. Clinical (Tier 1): 80%+ adoption, OWASP-backed → AUTO-PROMOTE
/// 3. Emerging (Tier 2): 50%+ adoption → REQUIRES SKILL REVIEW
/// 4. Below threshold → SKIP (surface in aphoria-suggest only)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorpusPromotionThresholds {
    /// Tier 0: Regulatory (95%+ adoption, RFC-backed) - AUTO-PROMOTE
    pub regulatory: PromotionCriteria,

    /// Tier 1: Clinical/Best Practice (80%+ adoption, OWASP-backed) - AUTO-PROMOTE
    pub clinical: PromotionCriteria,

    /// Tier 2: Emerging (50%+ adoption) - REQUIRES SKILL REVIEW
    pub emerging: PromotionCriteria,
}

impl Default for CorpusPromotionThresholds {
    fn default() -> Self {
        Self {
            regulatory: PromotionCriteria {
                min_projects: 850,
                min_adoption_rate: 0.95,
                require_authority: true,
                authority_sources: vec!["rfc://".to_string(), "nist://".to_string()],
                auto_promote: true,
                require_review: false,
            },
            clinical: PromotionCriteria {
                min_projects: 100,
                min_adoption_rate: 0.80,
                require_authority: true,
                authority_sources: vec!["owasp://".to_string(), "cwe://".to_string()],
                auto_promote: true,
                require_review: false,
            },
            emerging: PromotionCriteria {
                min_projects: 50,
                min_adoption_rate: 0.50,
                require_authority: false,
                authority_sources: vec![],
                auto_promote: false,
                require_review: true,
            },
        }
    }
}

/// Decision result from evaluating a pattern against promotion thresholds.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PromotionDecision {
    /// Auto-promote to the specified source class.
    AutoPromote(SourceClass),

    /// Pattern meets emerging threshold but requires manual review.
    RequireReview,

    /// Pattern below promotion threshold, surface in aphoria-suggest only.
    SuggestOnly,

    /// Pattern below all thresholds, skip entirely.
    Skip,
}

impl CorpusPromotionThresholds {
    /// Evaluate a pattern against the promotion thresholds.
    ///
    /// # Arguments
    ///
    /// * `project_count` - Number of projects reporting this pattern
    /// * `total_projects` - Total number of projects scanned
    /// * `has_authority_match` - Whether pattern matches an authority source
    /// * `authority_scheme` - The authority scheme if matched (e.g., "rfc://")
    ///
    /// # Returns
    ///
    /// A promotion decision indicating how to handle this pattern.
    pub fn evaluate(
        &self,
        project_count: u64,
        total_projects: u64,
        has_authority_match: bool,
        authority_scheme: Option<&str>,
    ) -> PromotionDecision {
        if total_projects == 0 {
            return PromotionDecision::Skip;
        }

        let adoption_rate = project_count as f64 / total_projects as f64;

        // Check Tier 0: Regulatory (highest priority)
        if adoption_rate >= self.regulatory.min_adoption_rate
            && project_count >= self.regulatory.min_projects
            && (!self.regulatory.require_authority
                || self.matches_authority(&self.regulatory, has_authority_match, authority_scheme))
        {
            return PromotionDecision::AutoPromote(SourceClass::Regulatory);
        }

        // Check Tier 1: Clinical
        if adoption_rate >= self.clinical.min_adoption_rate
            && project_count >= self.clinical.min_projects
            && (!self.clinical.require_authority
                || self.matches_authority(&self.clinical, has_authority_match, authority_scheme))
        {
            return PromotionDecision::AutoPromote(SourceClass::Clinical);
        }

        // Check Tier 2: Emerging (requires review)
        if adoption_rate >= self.emerging.min_adoption_rate
            && project_count >= self.emerging.min_projects
        {
            return PromotionDecision::RequireReview;
        }

        // Below all thresholds - suggest only
        if adoption_rate >= 0.25 && project_count >= 10 {
            return PromotionDecision::SuggestOnly;
        }

        PromotionDecision::Skip
    }

    /// Check if pattern matches required authority sources.
    fn matches_authority(
        &self,
        criteria: &PromotionCriteria,
        has_match: bool,
        scheme: Option<&str>,
    ) -> bool {
        if !has_match {
            return false;
        }

        if criteria.authority_sources.is_empty() {
            return true; // Any authority match is acceptable
        }

        if let Some(scheme) = scheme {
            criteria.authority_sources.iter().any(|required| scheme.starts_with(required))
        } else {
            false
        }
    }
}

/// Scale tier based on total projects in organization
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScaleTier {
    /// 1-5 projects: Very small teams
    Micro,
    /// 6-25 projects: Small teams
    Small,
    /// 26-100 projects: Medium organizations
    Medium,
    /// 101-500 projects: Large organizations
    Large,
    /// 501+ projects: Enterprise scale
    Enterprise,
}

impl ScaleTier {
    /// Detect scale tier from total project count
    pub fn from_total_projects(total: u64) -> Self {
        match total {
            0..=5 => Self::Micro,
            6..=25 => Self::Small,
            26..=100 => Self::Medium,
            101..=500 => Self::Large,
            _ => Self::Enterprise,
        }
    }
}

/// Adaptive promotion criteria that scales with team size
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptiveCriteria {
    /// Absolute minimum projects (safety floor)
    pub min_projects_floor: u64,
    /// Percentage of total projects required (scale factor)
    pub min_projects_percentage: f64,
    /// Minimum adoption rate (0.0-1.0)
    pub min_adoption_rate: f64,
    /// Whether authority source match is required
    pub require_authority: bool,
    /// List of authority source prefixes (e.g., ["rfc://", "nist://"])
    pub authority_sources: Vec<String>,
    /// Whether to auto-promote or require manual review
    pub auto_promote: bool,
}

impl AdaptiveCriteria {
    /// Calculate effective minimum projects for current total
    ///
    /// Returns max(floor, percentage * total) to ensure:
    /// - Small teams: percentage dominates (scales with growth)
    /// - Large teams: floor dominates (maintains quality)
    pub fn effective_min_projects(&self, total_projects: u64) -> u64 {
        let from_percentage = (self.min_projects_percentage * total_projects as f64).ceil() as u64;
        self.min_projects_floor.max(from_percentage)
    }
}

impl Default for AdaptiveCriteria {
    fn default() -> Self {
        Self {
            min_projects_floor: 2,
            min_projects_percentage: 0.50,
            min_adoption_rate: 0.50,
            require_authority: false,
            authority_sources: vec![],
            auto_promote: false,
        }
    }
}

/// Thresholds for a specific scale tier
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TierThresholds {
    /// Regulatory tier (RFC, NIST, etc.) - may be disabled (None)
    pub regulatory: Option<AdaptiveCriteria>,
    /// Clinical tier (OWASP, CWE, etc.) - may be disabled (None)
    pub clinical: Option<AdaptiveCriteria>,
    /// Emerging tier (community patterns) - always enabled
    pub emerging: AdaptiveCriteria,
}

/// Scale-adaptive threshold system
///
/// Automatically adjusts promotion criteria based on organization size:
/// - Micro teams (2-3 projects): See patterns immediately
/// - Small teams: Lower thresholds, all tiers enabled
/// - Medium/Large: Balanced quality gates
/// - Enterprise: Strict thresholds (backward compatible)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScaleAdaptiveThresholds {
    /// Thresholds for micro teams (1-5 projects).
    pub micro: TierThresholds,
    /// Thresholds for small teams (6-25 projects).
    pub small: TierThresholds,
    /// Thresholds for medium organizations (26-100 projects).
    pub medium: TierThresholds,
    /// Thresholds for large organizations (101-500 projects).
    pub large: TierThresholds,
    /// Thresholds for enterprise scale (501+ projects).
    pub enterprise: TierThresholds,
}

impl ScaleAdaptiveThresholds {
    /// Get thresholds for a specific scale tier
    pub fn for_tier(&self, tier: ScaleTier) -> &TierThresholds {
        match tier {
            ScaleTier::Micro => &self.micro,
            ScaleTier::Small => &self.small,
            ScaleTier::Medium => &self.medium,
            ScaleTier::Large => &self.large,
            ScaleTier::Enterprise => &self.enterprise,
        }
    }

    /// Evaluate promotion decision for a pattern
    ///
    /// # Arguments
    /// - `project_count`: Number of projects pattern appears in
    /// - `total_projects`: Total projects in organization
    /// - `has_authority_match`: Whether pattern matches authority source
    /// - `authority_scheme`: Authority scheme if matched (e.g., "rfc://")
    pub fn evaluate(
        &self,
        project_count: u64,
        total_projects: u64,
        has_authority_match: bool,
        authority_scheme: Option<&str>,
    ) -> PromotionDecision {
        if total_projects == 0 {
            return PromotionDecision::Skip;
        }

        let tier = ScaleTier::from_total_projects(total_projects);
        let thresholds = self.for_tier(tier);

        let adoption_rate = project_count as f64 / total_projects as f64;

        // Try regulatory (if enabled for this tier)
        if let Some(reg) = &thresholds.regulatory {
            let min_projects = reg.effective_min_projects(total_projects);
            if adoption_rate >= reg.min_adoption_rate
                && project_count >= min_projects
                && (!reg.require_authority
                    || matches_authority(has_authority_match, authority_scheme, &reg.authority_sources))
            {
                return PromotionDecision::AutoPromote(SourceClass::Regulatory);
            }
        }

        // Try clinical (if enabled)
        if let Some(clin) = &thresholds.clinical {
            let min_projects = clin.effective_min_projects(total_projects);
            if adoption_rate >= clin.min_adoption_rate
                && project_count >= min_projects
                && (!clin.require_authority
                    || matches_authority(has_authority_match, authority_scheme, &clin.authority_sources))
            {
                return PromotionDecision::AutoPromote(SourceClass::Clinical);
            }
        }

        // Try emerging (always enabled)
        let min_projects = thresholds.emerging.effective_min_projects(total_projects);
        if adoption_rate >= thresholds.emerging.min_adoption_rate && project_count >= min_projects {
            if thresholds.emerging.auto_promote {
                return PromotionDecision::AutoPromote(SourceClass::Community);
            } else {
                return PromotionDecision::RequireReview;
            }
        }

        PromotionDecision::Skip
    }
}

impl Default for ScaleAdaptiveThresholds {
    fn default() -> Self {
        Self {
            // Micro: 1-5 projects - Only emerging tier, very permissive
            micro: TierThresholds {
                regulatory: None, // Disabled
                clinical: None,   // Disabled
                emerging: AdaptiveCriteria {
                    min_projects_floor: 2,
                    min_projects_percentage: 0.50, // Pattern in 50% of projects
                    min_adoption_rate: 0.50,
                    require_authority: false,
                    authority_sources: vec![],
                    auto_promote: true, // Auto-promote for immediate visibility
                },
            },

            // Small: 6-25 projects - All tiers enabled, lower floors
            small: TierThresholds {
                regulatory: Some(AdaptiveCriteria {
                    min_projects_floor: 5,
                    min_projects_percentage: 0.90,
                    min_adoption_rate: 0.90,
                    require_authority: true,
                    authority_sources: vec!["rfc://".into(), "nist://".into()],
                    auto_promote: true,
                }),
                clinical: Some(AdaptiveCriteria {
                    min_projects_floor: 4,
                    min_projects_percentage: 0.75,
                    min_adoption_rate: 0.75,
                    require_authority: true,
                    authority_sources: vec!["owasp://".into(), "cwe://".into()],
                    auto_promote: true,
                }),
                emerging: AdaptiveCriteria {
                    min_projects_floor: 2,
                    min_projects_percentage: 0.40,
                    min_adoption_rate: 0.40,
                    require_authority: false,
                    authority_sources: vec![],
                    auto_promote: true, // Auto-promote for small teams too
                },
            },

            // Medium: 26-100 projects - Balanced thresholds
            medium: TierThresholds {
                regulatory: Some(AdaptiveCriteria {
                    min_projects_floor: 20,
                    min_projects_percentage: 0.90,
                    min_adoption_rate: 0.90,
                    require_authority: true,
                    authority_sources: vec!["rfc://".into(), "nist://".into()],
                    auto_promote: true,
                }),
                clinical: Some(AdaptiveCriteria {
                    min_projects_floor: 10,
                    min_projects_percentage: 0.75,
                    min_adoption_rate: 0.75,
                    require_authority: true,
                    authority_sources: vec!["owasp://".into(), "cwe://".into()],
                    auto_promote: true,
                }),
                emerging: AdaptiveCriteria {
                    min_projects_floor: 5,
                    min_projects_percentage: 0.40,
                    min_adoption_rate: 0.40,
                    require_authority: false,
                    authority_sources: vec![],
                    auto_promote: false,
                },
            },

            // Large: 101-500 projects - Higher quality gates
            large: TierThresholds {
                regulatory: Some(AdaptiveCriteria {
                    min_projects_floor: 50,
                    min_projects_percentage: 0.90,
                    min_adoption_rate: 0.90,
                    require_authority: true,
                    authority_sources: vec!["rfc://".into(), "nist://".into()],
                    auto_promote: true,
                }),
                clinical: Some(AdaptiveCriteria {
                    min_projects_floor: 30,
                    min_projects_percentage: 0.75,
                    min_adoption_rate: 0.75,
                    require_authority: true,
                    authority_sources: vec!["owasp://".into(), "cwe://".into()],
                    auto_promote: true,
                }),
                emerging: AdaptiveCriteria {
                    min_projects_floor: 15,
                    min_projects_percentage: 0.40,
                    min_adoption_rate: 0.40,
                    require_authority: false,
                    authority_sources: vec![],
                    auto_promote: false,
                },
            },

            // Enterprise: 501+ projects - Current defaults (backward compatible)
            enterprise: TierThresholds {
                regulatory: Some(AdaptiveCriteria {
                    min_projects_floor: 100,
                    min_projects_percentage: 0.95,
                    min_adoption_rate: 0.95,
                    require_authority: true,
                    authority_sources: vec!["rfc://".into(), "nist://".into()],
                    auto_promote: true,
                }),
                clinical: Some(AdaptiveCriteria {
                    min_projects_floor: 50,
                    min_projects_percentage: 0.80,
                    min_adoption_rate: 0.80,
                    require_authority: true,
                    authority_sources: vec!["owasp://".into(), "cwe://".into()],
                    auto_promote: true,
                }),
                emerging: AdaptiveCriteria {
                    min_projects_floor: 25,
                    min_projects_percentage: 0.50,
                    min_adoption_rate: 0.50,
                    require_authority: false,
                    authority_sources: vec![],
                    auto_promote: false,
                },
            },
        }
    }
}

/// Helper: Check if authority sources match
fn matches_authority(
    has_authority_match: bool,
    authority_scheme: Option<&str>,
    required_sources: &[String],
) -> bool {
    if !has_authority_match {
        return false;
    }

    if required_sources.is_empty() {
        return true; // Any authority source acceptable
    }

    if let Some(scheme) = authority_scheme {
        required_sources.iter().any(|src| scheme.starts_with(src))
    } else {
        false
    }
}

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

    #[test]
    fn test_default_thresholds() {
        let thresholds = CorpusPromotionThresholds::default();

        // Regulatory: 95%+ adoption, RFC-backed
        assert_eq!(thresholds.regulatory.min_adoption_rate, 0.95);
        assert_eq!(thresholds.regulatory.min_projects, 850);
        assert!(thresholds.regulatory.require_authority);
        assert!(thresholds.regulatory.auto_promote);

        // Clinical: 80%+ adoption, OWASP-backed
        assert_eq!(thresholds.clinical.min_adoption_rate, 0.80);
        assert_eq!(thresholds.clinical.min_projects, 100);
        assert!(thresholds.clinical.require_authority);
        assert!(thresholds.clinical.auto_promote);

        // Emerging: 50%+ adoption, review required
        assert_eq!(thresholds.emerging.min_adoption_rate, 0.50);
        assert_eq!(thresholds.emerging.min_projects, 50);
        assert!(!thresholds.emerging.require_authority);
        assert!(!thresholds.emerging.auto_promote);
        assert!(thresholds.emerging.require_review);
    }

    #[test]
    fn test_evaluate_regulatory_auto_promote() {
        let thresholds = CorpusPromotionThresholds::default();

        // 950 out of 1000 projects = 95% adoption with RFC match
        let decision = thresholds.evaluate(950, 1000, true, Some("rfc://5246"));

        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));
    }

    #[test]
    fn test_evaluate_regulatory_without_authority() {
        let thresholds = CorpusPromotionThresholds::default();

        // 950 out of 1000 projects = 95% adoption but NO authority match
        let decision = thresholds.evaluate(950, 1000, false, None);

        // Should not auto-promote to Regulatory without authority
        assert_ne!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));
    }

    #[test]
    fn test_evaluate_clinical_auto_promote() {
        let thresholds = CorpusPromotionThresholds::default();

        // 850 out of 1000 projects = 85% adoption with OWASP match
        let decision = thresholds.evaluate(850, 1000, true, Some("owasp://secrets"));

        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Clinical));
    }

    #[test]
    fn test_evaluate_emerging_requires_review() {
        let thresholds = CorpusPromotionThresholds::default();

        // 600 out of 1000 projects = 60% adoption, no authority
        let decision = thresholds.evaluate(600, 1000, false, None);

        assert_eq!(decision, PromotionDecision::RequireReview);
    }

    #[test]
    fn test_evaluate_suggest_only() {
        let thresholds = CorpusPromotionThresholds::default();

        // 300 out of 1000 projects = 30% adoption
        let decision = thresholds.evaluate(300, 1000, false, None);

        assert_eq!(decision, PromotionDecision::SuggestOnly);
    }

    #[test]
    fn test_evaluate_skip() {
        let thresholds = CorpusPromotionThresholds::default();

        // 50 out of 1000 projects = 5% adoption
        let decision = thresholds.evaluate(50, 1000, false, None);

        assert_eq!(decision, PromotionDecision::Skip);
    }

    #[test]
    fn test_evaluate_zero_projects() {
        let thresholds = CorpusPromotionThresholds::default();

        let decision = thresholds.evaluate(0, 0, false, None);

        assert_eq!(decision, PromotionDecision::Skip);
    }

    #[test]
    fn test_authority_scheme_matching() {
        let thresholds = CorpusPromotionThresholds::default();

        // RFC scheme should match regulatory
        let decision = thresholds.evaluate(950, 1000, true, Some("rfc://8996"));
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));

        // NIST scheme should also match regulatory
        let decision = thresholds.evaluate(950, 1000, true, Some("nist://sp800-53"));
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));

        // CWE scheme should match clinical
        let decision = thresholds.evaluate(850, 1000, true, Some("cwe://79"));
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Clinical));
    }

    #[test]
    fn test_min_projects_requirement() {
        let thresholds = CorpusPromotionThresholds::default();

        // 100% adoption but only 10 projects (below min_projects=850)
        let decision = thresholds.evaluate(10, 10, true, Some("rfc://5246"));

        // Should not promote to Regulatory due to min_projects
        assert_ne!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));
    }

    // ===== Scale-Adaptive Tests =====

    #[test]
    fn test_scale_tier_detection() {
        assert_eq!(ScaleTier::from_total_projects(1), ScaleTier::Micro);
        assert_eq!(ScaleTier::from_total_projects(3), ScaleTier::Micro);
        assert_eq!(ScaleTier::from_total_projects(5), ScaleTier::Micro);
        assert_eq!(ScaleTier::from_total_projects(6), ScaleTier::Small);
        assert_eq!(ScaleTier::from_total_projects(25), ScaleTier::Small);
        assert_eq!(ScaleTier::from_total_projects(26), ScaleTier::Medium);
        assert_eq!(ScaleTier::from_total_projects(100), ScaleTier::Medium);
        assert_eq!(ScaleTier::from_total_projects(101), ScaleTier::Large);
        assert_eq!(ScaleTier::from_total_projects(500), ScaleTier::Large);
        assert_eq!(ScaleTier::from_total_projects(501), ScaleTier::Enterprise);
        assert_eq!(ScaleTier::from_total_projects(10000), ScaleTier::Enterprise);
    }

    #[test]
    fn test_effective_min_projects() {
        let criteria = AdaptiveCriteria {
            min_projects_floor: 5,
            min_projects_percentage: 0.50,
            ..Default::default()
        };

        // Floor dominates for small counts
        assert_eq!(criteria.effective_min_projects(3), 5); // 50% * 3 = 1.5 → 2 < 5
        assert_eq!(criteria.effective_min_projects(8), 5); // 50% * 8 = 4 < 5

        // Percentage dominates for larger counts
        assert_eq!(criteria.effective_min_projects(12), 6); // 50% * 12 = 6 > 5
        assert_eq!(criteria.effective_min_projects(20), 10); // 50% * 20 = 10 > 5
    }

    #[test]
    fn test_micro_team_promotion() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 3 projects total, pattern in 2 projects (67% adoption)
        let decision = thresholds.evaluate(2, 3, false, None);

        // Should promote to emerging: max(2, 0.50*3) = 2, adoption = 67% >= 50%
        assert_eq!(decision, PromotionDecision::RequireReview);
    }

    #[test]
    fn test_micro_team_below_threshold() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 3 projects total, pattern in 1 project (33% adoption)
        let decision = thresholds.evaluate(1, 3, false, None);

        // Should NOT promote: 33% < 50% adoption rate
        assert_eq!(decision, PromotionDecision::Skip);
    }

    #[test]
    fn test_regulatory_disabled_for_micro() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 3 projects total, pattern in 3 projects (100% adoption, RFC match)
        let decision = thresholds.evaluate(3, 3, true, Some("rfc://1234"));

        // Should NOT promote to regulatory (disabled for micro tier)
        // Should promote to emerging instead
        assert_eq!(decision, PromotionDecision::RequireReview);
    }

    #[test]
    fn test_small_team_with_authority() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 10 projects total, pattern in 9 (90% adoption, RFC match)
        let decision = thresholds.evaluate(9, 10, true, Some("rfc://1234"));

        // Small tier regulatory: max(5, 0.90*10) = 9, rate = 90%
        // Should auto-promote to regulatory
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));
    }

    #[test]
    fn test_small_team_emerging() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 10 projects total, pattern in 4 (40% adoption, no authority)
        let decision = thresholds.evaluate(4, 10, false, None);

        // Small tier emerging: max(2, 0.40*10) = 4, rate = 40%
        // Should require review
        assert_eq!(decision, PromotionDecision::RequireReview);
    }

    #[test]
    fn test_medium_team_clinical() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 50 projects total, pattern in 38 (76% adoption, OWASP match)
        let decision = thresholds.evaluate(38, 50, true, Some("owasp://top-10/a01"));

        // Medium tier clinical: max(10, 0.75*50) = 37.5 → 38, rate = 76%
        // Should auto-promote to clinical
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Clinical));
    }

    #[test]
    fn test_enterprise_backward_compatible() {
        let thresholds = ScaleAdaptiveThresholds::default();

        // 1000 projects total, pattern in 950 (95% adoption, RFC match)
        let decision = thresholds.evaluate(950, 1000, true, Some("rfc://9110"));

        // Enterprise tier: max(100, 0.95*1000) = 950, rate = 95%
        // Should auto-promote to regulatory (same as legacy behavior)
        assert_eq!(decision, PromotionDecision::AutoPromote(SourceClass::Regulatory));
    }

    #[test]
    fn test_authority_matching() {
        // RFC source matches regulatory
        assert!(matches_authority(true, Some("rfc://9110"), &["rfc://".into(), "nist://".into()]));

        // NIST source matches regulatory
        assert!(matches_authority(true, Some("nist://sp800-53"), &["rfc://".into(), "nist://".into()]));

        // OWASP doesn't match regulatory
        assert!(!matches_authority(true, Some("owasp://top-10/a01"), &["rfc://".into(), "nist://".into()]));

        // No authority doesn't match when required
        assert!(!matches_authority(false, None, &["rfc://".into()]));

        // Empty sources accepts any authority
        assert!(matches_authority(true, Some("anything://"), &[]));
    }
}
