//! OWASP cheat sheet parsers.
//!
//! Contains topic-specific parsers for extracting security recommendations
//! from OWASP Cheat Sheets.

use regex::Regex;
use stemedb_core::types::ObjectValue;

/// A parsed security recommendation from a cheat sheet.
pub(super) struct Recommendation {
    /// Subject path (owasp://cheatsheet/{topic}/{section}/{claim}).
    pub subject: String,
    /// Predicate for the recommendation.
    pub predicate: String,
    /// Value extracted from the recommendation.
    pub value: ObjectValue,
    /// Human-readable description.
    pub description: String,
}

/// Parse security recommendations from cheat sheet markdown.
pub(super) fn parse_cheatsheet(content: &str, topic: &str) -> Vec<Recommendation> {
    let mut recommendations = Vec::new();

    // Parse based on topic
    match topic {
        "authentication" => recommendations.extend(parse_authentication_sheet(content)),
        "jwt" => recommendations.extend(parse_jwt_sheet(content)),
        "tls" => recommendations.extend(parse_tls_sheet(content)),
        "secrets" => recommendations.extend(parse_secrets_sheet(content)),
        "input_validation" => recommendations.extend(parse_input_validation_sheet(content)),
        "session" => recommendations.extend(parse_session_sheet(content)),
        "csrf" => recommendations.extend(parse_csrf_sheet(content)),
        "password_storage" => recommendations.extend(parse_password_storage_sheet(content)),
        "http_headers" => recommendations.extend(parse_http_headers_sheet(content)),
        _ => recommendations.extend(parse_generic_sheet(content, topic)),
    }

    recommendations
}

/// Parse authentication cheat sheet.
fn parse_authentication_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Multi-factor authentication
    if content.contains("multi-factor") || content.contains("MFA") || content.contains("2FA") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/authentication/mfa".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Multi-factor authentication SHOULD be implemented".to_string(),
        });
    }

    // Password requirements
    if content.contains("password") && content.contains("minimum") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/authentication/password_length".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Number(8.0),
            description: "OWASP: Minimum password length of 8 characters".to_string(),
        });
    }

    // Account lockout
    if content.contains("lockout") || content.contains("brute") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/authentication/account_lockout".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Account lockout SHOULD be enabled for brute force protection"
                .to_string(),
        });
    }

    // Secure password storage
    if content.contains("bcrypt") || content.contains("Argon2") || content.contains("hash") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/authentication/password_hashing".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("bcrypt_or_argon2".to_string()),
            description: "OWASP: Use bcrypt or Argon2 for password hashing".to_string(),
        });
    }

    recs
}

/// Parse JWT cheat sheet.
fn parse_jwt_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Algorithm validation
    if content.contains("algorithm") || content.contains("alg") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/jwt/algorithm_validation".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: JWT algorithm MUST be validated server-side".to_string(),
        });
    }

    // None algorithm rejection
    if content.contains("\"none\"") || content.contains("none algorithm") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/jwt/none_algorithm".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(false),
            description: "OWASP: JWT 'none' algorithm MUST be rejected".to_string(),
        });
    }

    // Expiration validation
    if content.contains("expiration") || content.contains("exp") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/jwt/expiration".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: JWT expiration MUST be validated".to_string(),
        });
    }

    // Signature verification
    if content.contains("signature") && content.contains("verify") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/jwt/signature_verification".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: JWT signatures MUST be verified".to_string(),
        });
    }

    recs
}

/// Parse TLS cheat sheet.
fn parse_tls_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // TLS version
    if content.contains("TLS 1.2") || content.contains("TLS 1.3") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/tls/min_version".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("TLS1.2".to_string()),
            description: "OWASP: Minimum TLS version should be 1.2".to_string(),
        });
    }

    // Certificate verification
    if content.contains("certificate") && content.contains("verify") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/tls/cert_verification".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: TLS certificates MUST be verified".to_string(),
        });
    }

    // Cipher suites
    if content.contains("cipher") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/tls/cipher_suites".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("strong_ciphers_only".to_string()),
            description: "OWASP: Only strong cipher suites should be enabled".to_string(),
        });
    }

    // HSTS
    if content.contains("HSTS") || content.contains("Strict-Transport-Security") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/tls/hsts".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: HSTS header SHOULD be enabled".to_string(),
        });
    }

    recs
}

/// Parse secrets management cheat sheet.
fn parse_secrets_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // No hardcoded secrets
    if content.contains("hardcoded") || content.contains("hardcode") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/secrets/hardcoded".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(false),
            description: "OWASP: Secrets MUST NOT be hardcoded".to_string(),
        });
    }

    // Environment variables or vault
    if content.contains("environment") || content.contains("vault") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/secrets/storage_method".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("environment_or_vault".to_string()),
            description: "OWASP: Secrets SHOULD be stored in environment variables or vault"
                .to_string(),
        });
    }

    // API key rotation
    if content.contains("rotation") || content.contains("rotate") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/secrets/rotation".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Secrets SHOULD be rotated regularly".to_string(),
        });
    }

    // Encryption at rest
    if content.contains("encrypt") && content.contains("rest") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/secrets/encryption_at_rest".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Secrets SHOULD be encrypted at rest".to_string(),
        });
    }

    recs
}

/// Parse input validation cheat sheet.
fn parse_input_validation_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Server-side validation
    if content.contains("server-side") || content.contains("server side") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/input_validation/server_side".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Input validation MUST be performed server-side".to_string(),
        });
    }

    // Allow list over deny list
    if content.contains("allowlist") || content.contains("whitelist") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/input_validation/allowlist".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Prefer allowlist over denylist for input validation".to_string(),
        });
    }

    // SQL injection prevention
    if content.contains("SQL") && content.contains("parameter") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/input_validation/parameterized_queries".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Use parameterized queries to prevent SQL injection".to_string(),
        });
    }

    // XSS prevention
    if content.contains("XSS") || content.contains("cross-site scripting") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/input_validation/output_encoding".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Output encoding MUST be used to prevent XSS".to_string(),
        });
    }

    recs
}

/// Parse session management cheat sheet.
fn parse_session_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Secure cookie flag
    if content.contains("Secure") && content.contains("cookie") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/session/secure_cookie".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Session cookies MUST have Secure flag".to_string(),
        });
    }

    // HttpOnly cookie flag
    if content.contains("HttpOnly") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/session/httponly_cookie".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Session cookies MUST have HttpOnly flag".to_string(),
        });
    }

    // Session timeout
    if content.contains("timeout") || content.contains("expiration") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/session/timeout".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Session timeout SHOULD be configured".to_string(),
        });
    }

    // Session regeneration
    if content.contains("regenerate") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/session/regeneration".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Session ID SHOULD be regenerated after authentication".to_string(),
        });
    }

    recs
}

/// Parse CSRF prevention cheat sheet.
fn parse_csrf_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // CSRF tokens
    if content.contains("token") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/csrf/token".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: CSRF tokens SHOULD be used".to_string(),
        });
    }

    // SameSite cookies
    if content.contains("SameSite") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/csrf/samesite_cookie".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("Strict".to_string()),
            description: "OWASP: SameSite cookie attribute SHOULD be Strict or Lax".to_string(),
        });
    }

    // Origin header validation
    if content.contains("Origin") && content.contains("header") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/csrf/origin_validation".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Origin header SHOULD be validated".to_string(),
        });
    }

    recs
}

/// Parse password storage cheat sheet.
fn parse_password_storage_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Argon2
    if content.contains("Argon2") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/password_storage/algorithm".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("Argon2id".to_string()),
            description: "OWASP: Argon2id is the recommended password hashing algorithm"
                .to_string(),
        });
    }

    // Salt
    if content.contains("salt") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/password_storage/salt".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Passwords MUST be salted before hashing".to_string(),
        });
    }

    // Work factor
    if content.contains("work factor") || content.contains("iterations") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/password_storage/work_factor".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Password hashing work factor SHOULD be configured".to_string(),
        });
    }

    recs
}

/// Parse HTTP headers cheat sheet.
fn parse_http_headers_sheet(content: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();

    // Content-Security-Policy
    if content.contains("Content-Security-Policy") || content.contains("CSP") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/http_headers/csp".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Content-Security-Policy header SHOULD be set".to_string(),
        });
    }

    // X-Content-Type-Options
    if content.contains("X-Content-Type-Options") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/http_headers/content_type_options".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("nosniff".to_string()),
            description: "OWASP: X-Content-Type-Options SHOULD be 'nosniff'".to_string(),
        });
    }

    // X-Frame-Options
    if content.contains("X-Frame-Options") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/http_headers/frame_options".to_string(),
            predicate: "config_value".to_string(),
            value: ObjectValue::Text("DENY".to_string()),
            description: "OWASP: X-Frame-Options SHOULD be 'DENY' or 'SAMEORIGIN'".to_string(),
        });
    }

    // Referrer-Policy
    if content.contains("Referrer-Policy") {
        recs.push(Recommendation {
            subject: "owasp://cheatsheet/http_headers/referrer_policy".to_string(),
            predicate: "enabled".to_string(),
            value: ObjectValue::Boolean(true),
            description: "OWASP: Referrer-Policy header SHOULD be set".to_string(),
        });
    }

    recs
}

/// Parse a generic cheat sheet using keyword matching.
fn parse_generic_sheet(content: &str, topic: &str) -> Vec<Recommendation> {
    let mut recs = Vec::new();
    let Ok(must_pattern) = Regex::new(r"(?i)\bMUST\b[^.]+\.") else { return recs };
    let Ok(should_pattern) = Regex::new(r"(?i)\bSHOULD\b[^.]+\.") else { return recs };

    for (i, cap) in must_pattern.captures_iter(content).enumerate().take(5) {
        let slug = create_slug(cap.get(0).map(|m| m.as_str()).unwrap_or(""));
        recs.push(Recommendation {
            subject: format!("owasp://cheatsheet/{}/must_{}", topic, i),
            predicate: "required".to_string(),
            value: ObjectValue::Boolean(true),
            description: format!("OWASP {}: {}", topic, truncate_description(&slug, 100)),
        });
    }
    for (i, cap) in should_pattern.captures_iter(content).enumerate().take(5) {
        let slug = create_slug(cap.get(0).map(|m| m.as_str()).unwrap_or(""));
        recs.push(Recommendation {
            subject: format!("owasp://cheatsheet/{}/should_{}", topic, i),
            predicate: "recommended".to_string(),
            value: ObjectValue::Boolean(true),
            description: format!("OWASP {}: {}", topic, truncate_description(&slug, 100)),
        });
    }
    recs
}

/// Create a URL-safe slug from text.
pub(super) fn create_slug(text: &str) -> String {
    text.to_lowercase()
        .chars()
        .map(|c| if c.is_alphanumeric() || c == ' ' { c } else { ' ' })
        .collect::<String>()
        .split_whitespace()
        .take(10)
        .collect::<Vec<_>>()
        .join("_")
}

/// Truncate description to max length.
pub(super) fn truncate_description(text: &str, max_len: usize) -> String {
    if text.len() <= max_len {
        text.to_string()
    } else {
        format!("{}...", &text[..max_len - 3])
    }
}
