//! Wiki-based corpus bootstrapping.
//!
//! This module provides the ability to bootstrap the corpus from markdown
//! documentation that contains MUST/SHOULD patterns with authority sources.
//!
//! # Example Wiki Format
//!
//! ```markdown
//! ## TLS Configuration
//!
//! TLS certificate verification MUST be enabled. Disabling verification
//! opens the application to man-in-the-middle attacks.
//!
//! Authority: RFC 5246 Section 7.4.2
//! ```
//!
//! This would be extracted as:
//! - Subject: `code://*/tls/cert_verification`
//! - Predicate: `enabled`
//! - Value: `Boolean(true)`
//! - Authority: `rfc://5246/7.4.2`

use std::path::Path;

use regex::Regex;
use tracing::{debug, instrument, warn};

use crate::community::{CommunityObjectValue, PatternAggregate};
use crate::AphoriaError;

/// A pattern extracted from wiki documentation.
#[derive(Debug, Clone)]
pub struct WikiPattern {
    /// Subject path (e.g., "tls/cert_verification")
    pub subject: String,
    /// Predicate (e.g., "enabled")
    pub predicate: String,
    /// Value extracted from pattern
    pub value: CommunityObjectValue,
    /// Authority source (e.g., "RFC 5246 Section 7.4.2")
    pub authority: Option<String>,
    /// Full text of the pattern statement
    pub statement: String,
}

impl WikiPattern {
    /// Convert to PatternAggregate with bootstrap counts.
    pub fn to_aggregate(&self, timestamp: u64) -> PatternAggregate {
        // Convert subject to full code:// path with wildcard
        let full_subject = if self.subject.starts_with("code://") {
            self.subject.clone()
        } else {
            format!("code://*/{}", self.subject)
        };

        PatternAggregate {
            subject: full_subject,
            predicate: self.predicate.clone(),
            value: self.value.clone(),
            project_count: 1, // Bootstrap count - will grow as real scans aggregate
            observation_count: 1,
            first_seen: timestamp,
            last_seen: timestamp,
        }
    }
}

/// Parser for wiki markdown files.
pub struct WikiParser {
    /// Regex for MUST/SHOULD patterns
    must_pattern: Regex,
    /// Regex for authority sources
    authority_pattern: Regex,
}

impl WikiParser {
    /// Create a new wiki parser.
    pub fn new() -> Result<Self, AphoriaError> {
        // Verbose regex mode with comments documenting each part
        let must_pattern = Regex::new(
            r#"(?ix)                                    # Case-insensitive, verbose mode
            (                                           # Capture group 1: Subject + security term
                (?:[A-Za-z0-9_/]+\s+)?                 # Optional subject prefix (e.g., "TLS ")
                (?:                                     # Security terms (one of):
                    certificate\s+verification |
                    TLS | SSL | JWT |
                    authentication | authorization |
                    encryption | hashing |
                    password | session | cookie |
                    CORS | CSP |
                    validation | sanitization
                )
            )\s+
            (MUST|SHOULD|MUST\s+NOT|SHOULD\s+NOT)\s+  # Capture group 2: Modal verb
            (?:be\s+)?                                  # Optional 'be' (e.g., "MUST be enabled")
            (                                           # Capture group 3: Action
                enabled | disabled | required |
                verified | enforced | used |
                set\s+to | configured
            )
            "#,
        )
        .map_err(|e| {
            AphoriaError::Config(format!("Failed to compile must_pattern regex: {}", e))
        })?;

        let authority_pattern = Regex::new(
            r"(?i)Authority:\s*(RFC\s+\d+(?:\s+Section\s+[\d.]+)?|OWASP\s+[\w\s-]+|CWE-\d+)",
        )
        .map_err(|e| {
            AphoriaError::Config(format!("Failed to compile authority_pattern regex: {}", e))
        })?;

        Ok(Self { must_pattern, authority_pattern })
    }

    /// Parse a markdown file and extract patterns.
    #[instrument(skip(self, content), fields(content_len = content.len()))]
    pub fn parse(&self, content: &str) -> Result<Vec<WikiPattern>, AphoriaError> {
        let mut patterns = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        for (i, line) in lines.iter().enumerate() {
            // Look for MUST/SHOULD patterns
            if let Some(captures) = self.must_pattern.captures(line) {
                let subject = captures.get(1).map(|m| m.as_str()).unwrap_or("");
                let modal = captures.get(2).map(|m| m.as_str()).unwrap_or("");
                let action = captures.get(3).map(|m| m.as_str()).unwrap_or("");

                // Determine predicate and value from modal + action
                let (predicate, value) = self.extract_predicate_value(modal, action);

                // Look for authority in nearby lines (next 5 lines)
                let authority = self.find_authority(&lines[i..std::cmp::min(i + 6, lines.len())]);

                let normalized_subject = self.normalize_subject(subject);

                debug!(
                    subject = %normalized_subject,
                    predicate = %predicate,
                    "Extracted pattern from wiki"
                );

                patterns.push(WikiPattern {
                    subject: normalized_subject,
                    predicate,
                    value,
                    authority,
                    statement: line.to_string(),
                });
            }
        }

        Ok(patterns)
    }

    /// Extract predicate and value from modal verb and action.
    fn extract_predicate_value(&self, modal: &str, action: &str) -> (String, CommunityObjectValue) {
        let normalized_modal = modal.to_uppercase();
        let normalized_action = action.to_lowercase();

        match normalized_action.as_str() {
            "enabled" | "enforced" | "required" | "verified" | "used" => {
                // MUST be enabled → enabled: true
                // MUST NOT be enabled → enabled: false
                let value = !normalized_modal.contains("NOT");
                ("enabled".to_string(), CommunityObjectValue::Boolean(value))
            }
            "disabled" => {
                // MUST be disabled → enabled: false
                // MUST NOT be disabled → enabled: true
                let value = normalized_modal.contains("NOT");
                ("enabled".to_string(), CommunityObjectValue::Boolean(value))
            }
            action_str
                if action_str.starts_with("set to") || action_str.starts_with("configured") =>
            {
                // For "set to X" or "configured to X", we'd need more context
                // For now, treat as enabled: true
                ("enabled".to_string(), CommunityObjectValue::Boolean(true))
            }
            _ => {
                // Default: enabled: true
                ("enabled".to_string(), CommunityObjectValue::Boolean(true))
            }
        }
    }

    /// Find authority source in nearby lines.
    fn find_authority(&self, lines: &[&str]) -> Option<String> {
        for line in lines {
            if let Some(captures) = self.authority_pattern.captures(line) {
                return captures.get(1).map(|m| m.as_str().to_string());
            }
        }
        None
    }

    /// Normalize subject path.
    fn normalize_subject(&self, subject: &str) -> String {
        // Convert "TLS certificate verification" → "tls/certificate_verification"
        // Convert "JWT/authentication" → "jwt/authentication"
        // Strategy: Known security terms (TLS, SSL, JWT, etc.) become path prefixes
        let security_terms = [
            "tls",
            "ssl",
            "jwt",
            "cors",
            "csp",
            "authentication",
            "authorization",
            "encryption",
            "hashing",
            "password",
            "session",
            "cookie",
            "validation",
            "sanitization",
        ];

        let normalized = subject.trim().to_lowercase();

        // Split on existing slashes first
        let segments: Vec<String> = normalized
            .split('/')
            .map(|seg| seg.trim())
            .filter(|s| !s.is_empty())
            .map(|seg| {
                // Within each segment, check if it starts with a security term
                let words: Vec<&str> = seg.split_whitespace().collect();
                if words.is_empty() {
                    return String::new();
                }

                // If first word is a security term, split it from the rest
                if security_terms.contains(&words[0]) {
                    if words.len() == 1 {
                        words[0].to_string()
                    } else {
                        // "tls certificate verification" → "tls/certificate_verification"
                        format!("{}/{}", words[0], words[1..].join("_"))
                    }
                } else {
                    // No security term prefix, just join with underscores
                    words.join("_")
                }
            })
            .collect();

        // Flatten nested paths
        segments.join("/").split('/').filter(|s| !s.is_empty()).collect::<Vec<_>>().join("/")
    }
}

// Default impl removed - WikiParser::new() can fail during regex compilation,
// so callers must handle errors explicitly.

/// Import patterns from a wiki directory.
#[instrument(skip_all, fields(wiki_path = %wiki_path.as_ref().display()))]
pub async fn import_from_wiki<P: AsRef<Path>>(
    wiki_path: P,
    timestamp: u64,
) -> Result<Vec<PatternAggregate>, AphoriaError> {
    let parser = WikiParser::new()?;
    let mut aggregates = Vec::new();

    let wiki_path = wiki_path.as_ref();
    if !wiki_path.exists() {
        return Err(AphoriaError::Config(format!(
            "Wiki path does not exist: {}",
            wiki_path.display()
        )));
    }

    // Walk directory for markdown files
    let walker = ignore::WalkBuilder::new(wiki_path)
        .follow_links(true)
        .build()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.path()
                .extension()
                .and_then(|s| s.to_str())
                .map(|ext| ext == "md" || ext == "markdown")
                .unwrap_or(false)
        });

    for entry in walker {
        let path = entry.path();
        debug!(file = %path.display(), "Parsing wiki file");

        let content = std::fs::read_to_string(path).map_err(|e| {
            AphoriaError::Config(format!("Failed to read {}: {}", path.display(), e))
        })?;

        match parser.parse(&content) {
            Ok(patterns) => {
                debug!(count = patterns.len(), "Extracted patterns from file");
                for pattern in patterns {
                    aggregates.push(pattern.to_aggregate(timestamp));
                }
            }
            Err(e) => {
                warn!(file = %path.display(), error = %e, "Failed to parse wiki file");
            }
        }
    }

    Ok(aggregates)
}

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

    #[test]
    fn test_parse_must_be_enabled() {
        let parser = WikiParser::new().expect("parser");
        let content = "TLS certificate verification MUST be enabled.";

        let patterns = parser.parse(content).expect("parse");
        assert_eq!(patterns.len(), 1);

        let pattern = &patterns[0];
        assert_eq!(pattern.subject, "tls/certificate_verification");
        assert_eq!(pattern.predicate, "enabled");
        assert_eq!(pattern.value, CommunityObjectValue::Boolean(true));
    }

    #[test]
    fn test_parse_must_not_be_disabled() {
        let parser = WikiParser::new().expect("parser");
        let content = "SSL certificate verification MUST NOT be disabled.";

        let patterns = parser.parse(content).expect("parse");
        assert_eq!(patterns.len(), 1);

        let pattern = &patterns[0];
        assert_eq!(pattern.predicate, "enabled");
        assert_eq!(pattern.value, CommunityObjectValue::Boolean(true));
    }

    #[test]
    fn test_parse_with_authority() {
        let parser = WikiParser::new().expect("parser");
        let content = r#"
TLS certificate verification MUST be enabled.

Authority: RFC 5246 Section 7.4.2
"#;

        let patterns = parser.parse(content).expect("parse");
        assert_eq!(patterns.len(), 1);

        let pattern = &patterns[0];
        assert_eq!(pattern.authority, Some("RFC 5246 Section 7.4.2".to_string()));
    }

    #[test]
    fn test_normalize_subject() {
        let parser = WikiParser::new().expect("parser");

        assert_eq!(parser.normalize_subject("TLS certificate"), "tls/certificate");
        assert_eq!(parser.normalize_subject("JWT/authentication"), "jwt/authentication");
        assert_eq!(parser.normalize_subject("  foo//bar  "), "foo/bar");
    }

    #[test]
    fn test_pattern_to_aggregate() {
        let pattern = WikiPattern {
            subject: "tls/cert".to_string(),
            predicate: "enabled".to_string(),
            value: CommunityObjectValue::Boolean(true),
            authority: Some("RFC 5246".to_string()),
            statement: "TLS MUST be enabled".to_string(),
        };

        let aggregate = pattern.to_aggregate(1706832000);
        assert_eq!(aggregate.subject, "code://*/tls/cert");
        assert_eq!(aggregate.predicate, "enabled");
        assert_eq!(aggregate.project_count, 1);
        assert_eq!(aggregate.observation_count, 1);
    }
}
