//! TLS certificate verification extractor.
//!
//! Detects patterns where TLS certificate verification is disabled,
//! which violates OWASP security guidelines.

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

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

/// Extractor for TLS certificate verification settings.
pub struct TlsVerifyExtractor {
    /// Rust: reqwest danger_accept_invalid_certs
    rust_reqwest: Regex,
    /// Rust: native-tls accept_invalid_certs
    rust_native_tls: Regex,
    /// Go: InsecureSkipVerify
    go_skip_verify: Regex,
    /// Python: requests verify=False
    python_verify: Regex,
    /// Node.js: rejectUnauthorized: false
    node_reject_unauthorized: Regex,
    /// Node.js: NODE_TLS_REJECT_UNAUTHORIZED=0
    node_env_reject: Regex,
    /// Generic YAML/TOML/JSON config
    config_verify: Regex,
}

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

impl TlsVerifyExtractor {
    /// Create a new TLS verify extractor with compiled regexes.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            rust_reqwest: Regex::new(r"danger_accept_invalid_certs\s*\(\s*true\s*\)")
                .expect("valid regex"),
            // Use TlsConnector or native-tls specific patterns (avoid matching reqwest's danger_ version)
            rust_native_tls: Regex::new(r"\.accept_invalid_certs\s*\(\s*true\s*\)")
                .expect("valid regex"),
            go_skip_verify: Regex::new(r"InsecureSkipVerify\s*:\s*true").expect("valid regex"),
            python_verify: Regex::new(r"verify\s*=\s*False").expect("valid regex"),
            node_reject_unauthorized: Regex::new(r"rejectUnauthorized\s*:\s*false")
                .expect("valid regex"),
            node_env_reject: Regex::new(r#"NODE_TLS_REJECT_UNAUTHORIZED.*['"]0['"]"#)
                .expect("valid regex"),
            // Expanded to include verify_certificates, cert_verify, skip_verify patterns
            // Also handles insecure_skip_verify: true (value true means verification IS skipped)
            config_verify: Regex::new(
                r"(?i)(tls_verify|ssl_verify|verify_ssl|verify_tls|verify_certificates?|cert_verify|certificate_verify)\s*[:=]\s*(false|no|0|off)|(insecure_skip_verify|skip_tls_verify|skip_ssl_verify)\s*[:=]\s*(true|yes|1|on)",
            )
            .expect("valid regex"),
        }
    }

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

        for (line_idx, line) in content.lines().enumerate() {
            if let Some(matched) = pattern.find(line) {
                let mut concept_path = path_segments.to_vec();
                concept_path.push("tls".to_string());
                concept_path.push("cert_verification".to_string());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "enabled".to_string(),
                    value: ObjectValue::Boolean(false),
                    file: file.to_string(),
                    line: line_idx + 1,
                    matched_text: matched.as_str().to_string(),
                    confidence: 1.0,
                    description: "TLS certificate verification is disabled".to_string(),
                });
            }
        }

        claims
    }
}

impl Extractor for TlsVerifyExtractor {
    fn name(&self) -> &str {
        "tls_verify"
    }

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

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

        match language {
            Language::Rust => {
                claims.extend(self.check_pattern(content, &self.rust_reqwest, path_segments, file));
                claims.extend(self.check_pattern(
                    content,
                    &self.rust_native_tls,
                    path_segments,
                    file,
                ));
            }
            Language::Go => {
                claims.extend(self.check_pattern(
                    content,
                    &self.go_skip_verify,
                    path_segments,
                    file,
                ));
            }
            Language::Python => {
                claims.extend(self.check_pattern(
                    content,
                    &self.python_verify,
                    path_segments,
                    file,
                ));
            }
            Language::TypeScript | Language::JavaScript => {
                claims.extend(self.check_pattern(
                    content,
                    &self.node_reject_unauthorized,
                    path_segments,
                    file,
                ));
                claims.extend(self.check_pattern(
                    content,
                    &self.node_env_reject,
                    path_segments,
                    file,
                ));
            }
            Language::Yaml | Language::Toml | Language::Json => {
                claims.extend(self.check_pattern(
                    content,
                    &self.config_verify,
                    path_segments,
                    file,
                ));
            }
            _ => {}
        }

        claims
    }
}

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

    #[test]
    fn test_rust_reqwest_detection() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
            let client = reqwest::Client::builder()
                .danger_accept_invalid_certs(true)
                .build()?;
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/client.rs");

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].predicate, "enabled");
        assert_eq!(claims[0].value, ObjectValue::Boolean(false));
        assert_eq!(claims[0].line, 3);
    }

    #[test]
    fn test_go_insecure_skip_verify() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
            tr := &http.Transport{
                TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
            }
        "#;

        let claims =
            extractor.extract(&["go".to_string()], content, Language::Go, "internal/http.go");

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

    #[test]
    fn test_python_verify_false() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
            response = requests.get(url, verify=False)
        "#;

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

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

    #[test]
    fn test_yaml_config() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
            http:
              tls_verify: false
        "#;

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

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

    #[test]
    fn test_no_false_positives() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
            let client = reqwest::Client::builder()
                .danger_accept_invalid_certs(false)
                .build()?;
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/client.rs");

        assert!(claims.is_empty());
    }

    #[test]
    fn test_verify_certificates_false() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
tls:
  verify_certificates: false
"#;

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

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

    #[test]
    fn test_insecure_skip_verify_true() {
        let extractor = TlsVerifyExtractor::new();
        let content = r#"
tls:
  insecure_skip_verify: true
"#;

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

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