//! Missing security headers extractor.
//!
//! Detects patterns where security headers are explicitly disabled or
//! configured insecurely.

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

use super::traits::{build_claim, Extractor};
use crate::types::{ExtractedClaim, Language};

/// Extractor for missing or disabled security headers.
///
/// Detects patterns indicating insecure header configurations:
/// - X-Frame-Options disabled or set to ALLOWALL
/// - X-Content-Type-Options disabled
/// - X-XSS-Protection disabled
/// - HSTS disabled
/// - Content-Security-Policy disabled
pub struct SecurityHeadersExtractor {
    // Explicit header disabled
    header_disabled: Regex,

    // Django missing secure settings
    django_missing: Regex,

    // YAML headers disabled
    yaml_disabled: Regex,

    // Frame options ALLOWALL
    frame_allowall: Regex,

    // CSP disabled or unsafe
    csp_unsafe: Regex,

    // HSTS disabled
    hsts_disabled: Regex,
}

impl Default for SecurityHeadersExtractor {
    fn default() -> Self {
        Self::new()
    }
}

impl SecurityHeadersExtractor {
    /// Create a new security headers extractor with compiled regexes.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Explicit header disabled in various formats
            header_disabled: Regex::new(
                r#"(?i)(?:X-Frame-Options|X-Content-Type-Options|X-XSS-Protection)\s*[:=]\s*["']?(?:none|disabled?|false|off)["']?"#,
            )
            .expect("valid regex"),

            // Django missing secure settings
            django_missing: Regex::new(
                r#"(?i)SECURE_(?:BROWSER_XSS_FILTER|CONTENT_TYPE_NOSNIFF|HSTS_SECONDS|SSL_REDIRECT)\s*=\s*(?:False|0)"#,
            )
            .expect("valid regex"),

            // YAML headers disabled
            yaml_disabled: Regex::new(
                r#"(?i)(?:x_frame_options|xss_protection|content_type_nosniff|hsts)\s*:\s*(?:false|no|disabled?|off)"#,
            )
            .expect("valid regex"),

            // Frame options ALLOWALL (dangerous)
            frame_allowall: Regex::new(r#"(?i)X-Frame-Options\s*[:=]\s*["']?ALLOWALL"#)
                .expect("valid regex"),

            // CSP disabled or using unsafe-inline/unsafe-eval
            csp_unsafe: Regex::new(
                r#"(?i)(?:Content-Security-Policy|CSP)\s*[:=]\s*["']?(?:none|disabled?|.*unsafe-(?:inline|eval))"#,
            )
            .expect("valid regex"),

            // HSTS disabled or set to 0
            hsts_disabled: Regex::new(
                r#"(?i)(?:Strict-Transport-Security|HSTS|hsts_seconds)\s*[:=]\s*(?:["']?(?:none|disabled?|false|off)["']?|0)"#,
            )
            .expect("valid regex"),
        }
    }

    fn make_claim(
        path_segments: &[String],
        file: &str,
        line: usize,
        matched: &str,
        header: &str,
        description: &str,
    ) -> ExtractedClaim {
        build_claim(
            path_segments,
            &["http", "security_headers", header],
            "header_status",
            ObjectValue::Text("disabled".to_string()),
            file,
            line,
            matched,
            0.8,
            description,
        )
    }
}

impl Extractor for SecurityHeadersExtractor {
    fn name(&self) -> &str {
        "security_headers"
    }

    fn languages(&self) -> &[Language] {
        &[
            Language::Python,
            Language::JavaScript,
            Language::TypeScript,
            Language::Go,
            Language::Yaml,
            Language::Json,
            Language::Toml,
        ]
    }

    fn extract(
        &self,
        path_segments: &[String],
        content: &str,
        _language: Language,
        file: &str,
    ) -> Vec<ExtractedClaim> {
        let mut claims = Vec::new();

        for (line_idx, line) in content.lines().enumerate() {
            let line_num = line_idx + 1;

            // Check for explicitly disabled headers
            if let Some(m) = self.header_disabled.find(line) {
                let header = if line.to_lowercase().contains("frame") {
                    "x_frame_options"
                } else if line.to_lowercase().contains("content-type") {
                    "x_content_type_options"
                } else {
                    "x_xss_protection"
                };
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    header,
                    "Security header explicitly disabled",
                ));
            }

            // Check Django secure settings
            if let Some(m) = self.django_missing.find(line) {
                let header = if line.to_lowercase().contains("xss") {
                    "x_xss_protection"
                } else if line.to_lowercase().contains("nosniff") {
                    "x_content_type_options"
                } else if line.to_lowercase().contains("hsts") {
                    "hsts"
                } else {
                    "ssl_redirect"
                };
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    header,
                    "Django security setting disabled",
                ));
            }

            // Check YAML disabled patterns
            if let Some(m) = self.yaml_disabled.find(line) {
                let header = if line.to_lowercase().contains("frame") {
                    "x_frame_options"
                } else if line.to_lowercase().contains("xss") {
                    "x_xss_protection"
                } else if line.to_lowercase().contains("nosniff") {
                    "x_content_type_options"
                } else {
                    "hsts"
                };
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    header,
                    "Security header disabled in configuration",
                ));
            }

            // Check for dangerous X-Frame-Options ALLOWALL
            if let Some(m) = self.frame_allowall.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    "x_frame_options",
                    "X-Frame-Options set to ALLOWALL (clickjacking risk)",
                ));
            }

            // Check for CSP unsafe patterns
            if let Some(m) = self.csp_unsafe.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    "content_security_policy",
                    "Content-Security-Policy disabled or uses unsafe directives",
                ));
            }

            // Check for HSTS disabled
            if let Some(m) = self.hsts_disabled.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    m.as_str(),
                    "hsts",
                    "Strict-Transport-Security (HSTS) disabled",
                ));
            }
        }

        claims
    }
}

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

    #[test]
    fn test_header_disabled() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            X-Frame-Options: none
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Yaml, "nginx.conf");

        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("security_headers"));
    }

    #[test]
    fn test_django_missing() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            SECURE_BROWSER_XSS_FILTER = False
        "#;

        let claims =
            extractor.extract(&["python".to_string()], content, Language::Python, "settings.py");

        assert_eq!(claims.len(), 1);
        assert!(claims[0].description.contains("Django"));
    }

    #[test]
    fn test_yaml_disabled() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            x_frame_options: false
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Yaml, "config.yaml");

        assert_eq!(claims.len(), 1);
    }

    #[test]
    fn test_frame_allowall() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            X-Frame-Options = "ALLOWALL"
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Toml, "config.toml");

        assert_eq!(claims.len(), 1);
        assert!(claims[0].description.contains("clickjacking"));
    }

    #[test]
    fn test_csp_unsafe() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            Content-Security-Policy: script-src 'unsafe-inline'
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Yaml, "config.yaml");

        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("content_security_policy"));
    }

    #[test]
    fn test_hsts_disabled() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            Strict-Transport-Security: none
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Yaml, "config.yaml");

        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("hsts"));
    }

    #[test]
    fn test_hsts_zero() {
        let extractor = SecurityHeadersExtractor::new();
        let content = r#"
            HSTS_SECONDS = 0
        "#;

        let claims =
            extractor.extract(&["python".to_string()], content, Language::Python, "settings.py");

        // Should detect hsts_disabled pattern (HSTS = 0)
        assert_eq!(claims.len(), 1);
    }

    #[test]
    fn test_no_false_positives_enabled() {
        let extractor = SecurityHeadersExtractor::new();
        // Safe: headers enabled
        let content = r#"
            X-Frame-Options: SAMEORIGIN
            X-Content-Type-Options: nosniff
            SECURE_BROWSER_XSS_FILTER = True
        "#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Yaml, "config.yaml");

        assert!(claims.is_empty());
    }
}
