//! Core types for pattern promotion.
//!
//! When learned patterns reach critical mass, they become candidates
//! for promotion to declarative extractors.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::config::AutonomousConfig;
use crate::extractors::DeclarativeExtractorDef;
use crate::learning::LearnedPattern;

/// A pattern ready for promotion with a generated extractor.
///
/// Contains the original learned pattern, the generated declarative
/// extractor definition, and validation results.
#[derive(Debug, Clone)]
pub struct PromotionCandidate {
    /// The original learned pattern being promoted.
    pub pattern: LearnedPattern,

    /// The generated declarative extractor definition.
    pub extractor_def: DeclarativeExtractorDef,

    /// Validation results for the generated extractor.
    pub validation: ValidationResult,
}

impl PromotionCandidate {
    /// Create a new promotion candidate.
    pub fn new(
        pattern: LearnedPattern,
        extractor_def: DeclarativeExtractorDef,
        validation: ValidationResult,
    ) -> Self {
        Self { pattern, extractor_def, validation }
    }

    /// Check if this candidate is ready for promotion.
    ///
    /// A candidate is ready when validation passed and performance is acceptable.
    pub fn is_ready(&self) -> bool {
        self.validation.passed && self.validation.performance_ok
    }

    /// Check if this candidate qualifies for autonomous promotion.
    ///
    /// Autonomous promotion requires meeting stricter thresholds than
    /// regular promotion. This is the core decision function for Phase 9.
    ///
    /// # Requirements
    ///
    /// - Autonomous promotion must be enabled (kill switch)
    /// - Pattern must meet confidence threshold (default 0.95)
    /// - Pattern must meet project count threshold (default 10)
    /// - If `require_zero_failures`: no positive test failures
    /// - If `require_zero_warnings`: no validation warnings
    /// - No false positive warning flag
    /// - No performance warning flag
    /// - Validation must have passed
    /// - Performance must be acceptable
    pub fn should_auto_promote(&self, config: &AutonomousConfig) -> bool {
        // Kill switch is the first check
        if !config.enabled {
            return false;
        }

        // Check thresholds
        if self.pattern.avg_confidence < config.min_confidence {
            return false;
        }
        if self.pattern.project_count() < config.min_projects {
            return false;
        }

        // Check failure requirements
        if config.require_zero_failures && !self.validation.positive_failures.is_empty() {
            return false;
        }

        // Check warning requirements
        if config.require_zero_warnings && !self.validation.warnings.is_empty() {
            return false;
        }

        // Check explicit warning flags
        if self.validation.false_positive_warning || self.validation.performance_warning {
            return false;
        }

        // Must pass validation and performance
        self.validation.passed && self.validation.performance_ok
    }

    /// Get reasons why this candidate cannot be auto-promoted.
    ///
    /// Returns an empty vector if the candidate qualifies for auto-promotion.
    /// Used for audit logging and user feedback.
    pub fn auto_promotion_blockers(&self, config: &AutonomousConfig) -> Vec<String> {
        let mut blockers = Vec::new();

        if !config.enabled {
            blockers.push("Autonomous promotion is disabled".to_string());
            return blockers; // No need to check other conditions
        }

        if self.pattern.avg_confidence < config.min_confidence {
            blockers.push(format!(
                "Confidence {:.2} < {:.2} threshold",
                self.pattern.avg_confidence, config.min_confidence
            ));
        }

        if self.pattern.project_count() < config.min_projects {
            blockers.push(format!(
                "Projects {} < {} threshold",
                self.pattern.project_count(),
                config.min_projects
            ));
        }

        if config.require_zero_failures && !self.validation.positive_failures.is_empty() {
            blockers.push(format!(
                "{} positive test failures",
                self.validation.positive_failures.len()
            ));
        }

        if config.require_zero_warnings && !self.validation.warnings.is_empty() {
            blockers.push(format!("{} validation warnings", self.validation.warnings.len()));
        }

        if self.validation.false_positive_warning {
            blockers.push("False positive risk detected".to_string());
        }

        if self.validation.performance_warning {
            blockers.push("Performance concerns detected".to_string());
        }

        if !self.validation.passed {
            blockers.push("Validation did not pass".to_string());
        }

        if !self.validation.performance_ok {
            blockers.push("Performance is not acceptable".to_string());
        }

        blockers
    }

    /// Get the pattern ID.
    pub fn pattern_id(&self) -> Uuid {
        self.pattern.id
    }

    /// Get the generated extractor name.
    pub fn extractor_name(&self) -> &str {
        &self.extractor_def.name
    }
}

/// Result of validating a generated extractor.
///
/// Validation includes testing the regex against stored examples,
/// checking for ReDoS vulnerabilities, and measuring performance.
#[derive(Debug, Clone, Default)]
pub struct ValidationResult {
    /// Example strings that matched successfully.
    pub positive_matches: Vec<String>,

    /// Example strings that failed to match.
    pub positive_failures: Vec<String>,

    /// Whether all validation checks passed.
    pub passed: bool,

    /// Whether performance is acceptable.
    pub performance_ok: bool,

    /// Time to compile the regex in milliseconds.
    pub compile_time_ms: u64,

    /// Average time to match in microseconds.
    pub avg_match_time_us: u64,

    /// Any warnings generated during validation.
    pub warnings: Vec<String>,

    /// Explicit false positive warning flag.
    ///
    /// Set when the pattern shows characteristics that suggest
    /// a high risk of false positives (e.g., too broad, matches
    /// common code patterns).
    pub false_positive_warning: bool,

    /// Explicit performance warning flag.
    ///
    /// Set when the pattern shows characteristics that suggest
    /// performance problems (e.g., catastrophic backtracking risk,
    /// very slow compilation).
    pub performance_warning: bool,
}

impl ValidationResult {
    /// Create a successful validation result.
    pub fn success(
        positive_matches: Vec<String>,
        compile_time_ms: u64,
        avg_match_time_us: u64,
    ) -> Self {
        Self {
            positive_matches,
            positive_failures: vec![],
            passed: true,
            performance_ok: true,
            compile_time_ms,
            avg_match_time_us,
            warnings: vec![],
            false_positive_warning: false,
            performance_warning: false,
        }
    }

    /// Create a failed validation result.
    pub fn failure(positive_failures: Vec<String>, warnings: Vec<String>) -> Self {
        Self {
            positive_matches: vec![],
            positive_failures,
            passed: false,
            performance_ok: false,
            compile_time_ms: 0,
            avg_match_time_us: 0,
            warnings,
            false_positive_warning: false,
            performance_warning: false,
        }
    }

    /// Mark as having false positive risk.
    pub fn mark_false_positive_risk(&mut self, reason: impl Into<String>) {
        self.false_positive_warning = true;
        self.add_warning(reason);
    }

    /// Mark as having performance concerns.
    pub fn mark_performance_concern(&mut self, reason: impl Into<String>) {
        self.performance_warning = true;
        self.add_warning(reason);
    }

    /// Add a warning to the result.
    pub fn add_warning(&mut self, warning: impl Into<String>) {
        self.warnings.push(warning.into());
    }

    /// Mark performance as unacceptable.
    pub fn mark_slow(&mut self, reason: impl Into<String>) {
        self.performance_ok = false;
        self.add_warning(reason);
    }
}

/// Decision made during human review of a promotion candidate.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReviewDecision {
    /// Approve the candidate for promotion.
    Approve,

    /// Reject the candidate (won't be promoted).
    Reject,

    /// Request regeneration with different parameters.
    Regenerate,

    /// Skip for now (remain in candidates list).
    Skip,
}

impl ReviewDecision {
    /// Parse a decision from user input.
    pub fn from_input(input: &str) -> Option<Self> {
        match input.trim().to_lowercase().as_str() {
            "a" | "approve" | "y" | "yes" => Some(Self::Approve),
            "r" | "reject" | "n" | "no" => Some(Self::Reject),
            "g" | "regenerate" | "retry" => Some(Self::Regenerate),
            "s" | "skip" => Some(Self::Skip),
            _ => None,
        }
    }
}

/// Metadata about a promoted extractor.
///
/// This is serialized into the YAML output for traceability.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromotionMetadata {
    /// Source indicator (always "learned").
    pub source: String,

    /// ID of the original pattern.
    pub pattern_id: Uuid,

    /// Version number of this extractor.
    #[serde(default = "default_version")]
    pub version: u32,

    /// Number of projects where pattern was observed.
    pub projects: usize,

    /// Total number of occurrences.
    pub occurrences: u32,

    /// Average LLM confidence across observations.
    pub avg_confidence: f32,

    /// When the extractor was promoted.
    #[serde(with = "chrono::serde::ts_seconds")]
    pub promoted_at: DateTime<Utc>,

    /// Whether this was auto-promoted (no human review).
    #[serde(default)]
    pub auto_promoted: bool,

    /// Approver identity ("autonomous" for auto-promoted, agent ID otherwise).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub approved_by: Option<String>,
}

/// Default version number for extractors without version field.
fn default_version() -> u32 {
    1
}

impl PromotionMetadata {
    /// Create metadata from a learned pattern (human-reviewed).
    pub fn from_pattern(pattern: &LearnedPattern) -> Self {
        Self {
            source: "learned".to_string(),
            pattern_id: pattern.id,
            version: 1, // First version
            projects: pattern.project_count(),
            occurrences: pattern.occurrences,
            avg_confidence: pattern.avg_confidence,
            promoted_at: Utc::now(),
            auto_promoted: false,
            approved_by: None,
        }
    }

    /// Create metadata from a learned pattern with explicit version.
    pub fn from_pattern_versioned(pattern: &LearnedPattern, version: u32) -> Self {
        Self {
            source: "learned".to_string(),
            pattern_id: pattern.id,
            version,
            projects: pattern.project_count(),
            occurrences: pattern.occurrences,
            avg_confidence: pattern.avg_confidence,
            promoted_at: Utc::now(),
            auto_promoted: false,
            approved_by: None,
        }
    }

    /// Create metadata for an autonomously promoted pattern.
    ///
    /// Sets `auto_promoted: true` and `approved_by: "autonomous"`.
    pub fn from_autonomous(pattern: &LearnedPattern) -> Self {
        Self {
            source: "learned".to_string(),
            pattern_id: pattern.id,
            version: 1, // First version
            projects: pattern.project_count(),
            occurrences: pattern.occurrences,
            avg_confidence: pattern.avg_confidence,
            promoted_at: Utc::now(),
            auto_promoted: true,
            approved_by: Some("autonomous".to_string()),
        }
    }

    /// Create metadata for an autonomously promoted pattern with explicit version.
    pub fn from_autonomous_versioned(pattern: &LearnedPattern, version: u32) -> Self {
        Self {
            source: "learned".to_string(),
            pattern_id: pattern.id,
            version,
            projects: pattern.project_count(),
            occurrences: pattern.occurrences,
            avg_confidence: pattern.avg_confidence,
            promoted_at: Utc::now(),
            auto_promoted: true,
            approved_by: Some("autonomous".to_string()),
        }
    }
}

/// Statistics about the promotion pipeline.
#[derive(Debug, Clone, Default)]
pub struct PromotionStats {
    /// Total patterns in the store.
    pub total_patterns: usize,

    /// Patterns eligible for promotion.
    pub eligible_patterns: usize,

    /// Patterns already promoted.
    pub promoted_patterns: usize,

    /// Patterns pending review.
    pub pending_review: usize,

    /// Average confidence of eligible patterns.
    pub avg_confidence: f32,

    /// Average project count of eligible patterns.
    pub avg_projects: f32,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::extractors::{DeclarativeClaimDef, DeclarativeExtractorDef, DeclarativeValue};
    use crate::learning::{ClaimTemplate, ValueType};
    use crate::types::Language;

    fn create_test_pattern() -> LearnedPattern {
        LearnedPattern::new(
            "const TLS_MIN = \"1.0\"",
            "const TLS_MIN = <string:version>",
            ClaimTemplate::new("tls/min_version", "version", ValueType::Text, "TLS version"),
            Language::Rust,
            "project1",
            0.9,
        )
    }

    fn create_high_confidence_pattern() -> LearnedPattern {
        let mut pattern = LearnedPattern::new(
            "verify_ssl = false",
            "verify_ssl = <boolean>",
            ClaimTemplate::new("ssl/verify", "enabled", ValueType::Boolean, "SSL verification"),
            Language::Python,
            "project1",
            0.97,
        );
        // Add 10+ projects with high confidence
        for i in 2..=12 {
            pattern.record_observation(format!("project{}", i), 0.96, Utc::now());
        }
        pattern
    }

    fn create_test_extractor() -> DeclarativeExtractorDef {
        DeclarativeExtractorDef {
            name: "test_extractor".to_string(),
            description: "Test extractor".to_string(),
            languages: vec!["python".to_string()],
            pattern: r"verify_ssl\s*=\s*(?P<value>true|false)".to_string(),
            claim: DeclarativeClaimDef {
                subject: "ssl/verify".to_string(),
                predicate: "enabled".to_string(),
                value: DeclarativeValue::MatchedText { value_from_match: true },
            },
            confidence: 0.96,
            source: None,
        }
    }

    fn create_candidate(
        pattern: LearnedPattern,
        validation: ValidationResult,
    ) -> PromotionCandidate {
        PromotionCandidate::new(pattern, create_test_extractor(), validation)
    }

    #[test]
    fn test_validation_result_success() {
        let result = ValidationResult::success(vec!["match1".to_string()], 10, 50);
        assert!(result.passed);
        assert!(result.performance_ok);
        assert!(result.positive_failures.is_empty());
        assert!(result.warnings.is_empty());
    }

    #[test]
    fn test_validation_result_failure() {
        let result =
            ValidationResult::failure(vec!["failed1".to_string()], vec!["warning1".to_string()]);
        assert!(!result.passed);
        assert!(!result.performance_ok);
        assert_eq!(result.positive_failures.len(), 1);
        assert_eq!(result.warnings.len(), 1);
    }

    #[test]
    fn test_validation_result_add_warning() {
        let mut result = ValidationResult::success(vec![], 10, 50);
        result.add_warning("test warning");
        assert_eq!(result.warnings.len(), 1);
        assert_eq!(result.warnings[0], "test warning");
    }

    #[test]
    fn test_validation_result_mark_slow() {
        let mut result = ValidationResult::success(vec![], 10, 50);
        assert!(result.performance_ok);

        result.mark_slow("regex too slow");
        assert!(!result.performance_ok);
        assert!(result.warnings.contains(&"regex too slow".to_string()));
    }

    #[test]
    fn test_review_decision_from_input() {
        assert_eq!(ReviewDecision::from_input("a"), Some(ReviewDecision::Approve));
        assert_eq!(ReviewDecision::from_input("approve"), Some(ReviewDecision::Approve));
        assert_eq!(ReviewDecision::from_input("Y"), Some(ReviewDecision::Approve));

        assert_eq!(ReviewDecision::from_input("r"), Some(ReviewDecision::Reject));
        assert_eq!(ReviewDecision::from_input("reject"), Some(ReviewDecision::Reject));
        assert_eq!(ReviewDecision::from_input("N"), Some(ReviewDecision::Reject));

        assert_eq!(ReviewDecision::from_input("g"), Some(ReviewDecision::Regenerate));
        assert_eq!(ReviewDecision::from_input("retry"), Some(ReviewDecision::Regenerate));

        assert_eq!(ReviewDecision::from_input("s"), Some(ReviewDecision::Skip));
        assert_eq!(ReviewDecision::from_input("skip"), Some(ReviewDecision::Skip));

        assert_eq!(ReviewDecision::from_input("invalid"), None);
        assert_eq!(ReviewDecision::from_input(""), None);
    }

    #[test]
    fn test_promotion_metadata_from_pattern() {
        let mut pattern = create_test_pattern();
        pattern.record_observation("project2", 0.85, Utc::now());
        pattern.record_observation("project3", 0.8, Utc::now());

        let metadata = PromotionMetadata::from_pattern(&pattern);

        assert_eq!(metadata.source, "learned");
        assert_eq!(metadata.pattern_id, pattern.id);
        assert_eq!(metadata.projects, 3);
        assert_eq!(metadata.occurrences, 3);
        // Average of 0.9 + 0.85 + 0.8 = 0.85
        assert!((metadata.avg_confidence - 0.85).abs() < 0.01);
        assert!(!metadata.auto_promoted);
        assert!(metadata.approved_by.is_none());
    }

    #[test]
    fn test_promotion_metadata_from_autonomous() {
        let pattern = create_high_confidence_pattern();
        let metadata = PromotionMetadata::from_autonomous(&pattern);

        assert_eq!(metadata.source, "learned");
        assert!(metadata.auto_promoted);
        assert_eq!(metadata.approved_by, Some("autonomous".to_string()));
    }

    #[test]
    fn test_should_auto_promote_when_disabled() {
        let pattern = create_high_confidence_pattern();
        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        // Kill switch is off
        let config = AutonomousConfig { enabled: false, ..Default::default() };
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_meets_all_thresholds() {
        let pattern = create_high_confidence_pattern();
        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            require_zero_failures: true,
            require_zero_warnings: true,
            audit_log: true,
            audit_dir: None,
        };
        assert!(candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_below_confidence() {
        let mut pattern = create_test_pattern();
        // Add enough projects but lower confidence
        for i in 2..=12 {
            pattern.record_observation(format!("project{}", i), 0.85, Utc::now());
        }

        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            ..Default::default()
        };
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_below_projects() {
        let mut pattern = LearnedPattern::new(
            "verify_ssl = false",
            "verify_ssl = <boolean>",
            ClaimTemplate::new("ssl/verify", "enabled", ValueType::Boolean, "SSL verification"),
            Language::Python,
            "project1",
            0.97,
        );
        // Only 5 projects, need 10
        for i in 2..=5 {
            pattern.record_observation(format!("project{}", i), 0.96, Utc::now());
        }

        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            ..Default::default()
        };
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_with_failures() {
        let pattern = create_high_confidence_pattern();
        let validation = ValidationResult::failure(
            vec!["failed_match".to_string()],
            vec!["warning".to_string()],
        );
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            require_zero_failures: true,
            ..Default::default()
        };
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_with_warnings() {
        let pattern = create_high_confidence_pattern();
        let mut validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        validation.add_warning("potential false positive");
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            require_zero_warnings: true,
            ..Default::default()
        };
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_should_auto_promote_with_false_positive_warning() {
        let pattern = create_high_confidence_pattern();
        let mut validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        validation.mark_false_positive_risk("pattern too broad");
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            require_zero_warnings: false, // Allow warnings
            ..Default::default()
        };
        // Should still reject due to false_positive_warning flag
        assert!(!candidate.should_auto_promote(&config));
    }

    #[test]
    fn test_auto_promotion_blockers() {
        let pattern = create_test_pattern();
        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig {
            enabled: true,
            min_confidence: 0.95,
            min_projects: 10,
            ..Default::default()
        };

        let blockers = candidate.auto_promotion_blockers(&config);
        // Should have confidence and projects blockers
        assert!(blockers.iter().any(|b| b.contains("Confidence")));
        assert!(blockers.iter().any(|b| b.contains("Projects")));
    }

    #[test]
    fn test_auto_promotion_blockers_disabled() {
        let pattern = create_high_confidence_pattern();
        let validation = ValidationResult::success(vec!["match".to_string()], 10, 50);
        let candidate = create_candidate(pattern, validation);

        let config = AutonomousConfig { enabled: false, ..Default::default() };
        let blockers = candidate.auto_promotion_blockers(&config);
        assert_eq!(blockers.len(), 1);
        assert!(blockers[0].contains("disabled"));
    }

    #[test]
    fn test_validation_result_warning_flags() {
        let mut result = ValidationResult::success(vec![], 10, 50);
        assert!(!result.false_positive_warning);
        assert!(!result.performance_warning);

        result.mark_false_positive_risk("too broad");
        assert!(result.false_positive_warning);
        assert!(result.warnings.contains(&"too broad".to_string()));

        result.mark_performance_concern("slow regex");
        assert!(result.performance_warning);
        assert!(result.warnings.contains(&"slow regex".to_string()));
    }
}
