//! Insecure cookie flags extractor.
//!
//! Detects cookies set without proper security flags:
//! - Missing `Secure` flag (allows transmission over HTTP)
//! - Missing `HttpOnly` flag (vulnerable to XSS)
//! - `SameSite=None` without `Secure` (rejected by modern browsers)
//!
//! These are common misconfigurations that can lead to session hijacking.

mod patterns;

use stemedb_core::types::ObjectValue;

use self::patterns::CookiePatterns;
use super::{build_claim, Extractor};
use crate::types::{ExtractedClaim, Language};

/// Extractor for insecure cookie configuration patterns.
///
/// Detects cookies that may be vulnerable to interception or XSS attacks
/// due to missing security flags.
pub struct InsecureCookiesExtractor {
    patterns: CookiePatterns,
}

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

impl InsecureCookiesExtractor {
    /// Create a new insecure cookies extractor with compiled regexes.
    pub fn new() -> Self {
        Self { patterns: CookiePatterns::compile() }
    }

    fn make_claim(
        path_segments: &[String],
        file: &str,
        line: usize,
        matched_text: &str,
        issue_type: &str,
        description: &str,
    ) -> ExtractedClaim {
        build_claim(
            path_segments,
            &["cookies", issue_type],
            "cookie_security",
            ObjectValue::Text("insecure".to_string()),
            file,
            line,
            matched_text,
            1.0,
            description,
        )
    }
}

impl Extractor for InsecureCookiesExtractor {
    fn name(&self) -> &str {
        "insecure_cookies"
    }

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

    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;

            match language {
                Language::Python => {
                    // Python/Flask set_cookie with secure=False
                    if let Some(matched) = self.patterns.python_secure_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_secure",
                            "Cookie set with Secure=False - vulnerable to interception over HTTP",
                        ));
                    }

                    // Python/Flask set_cookie with httponly=False
                    if let Some(matched) = self.patterns.python_httponly_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_httponly",
                            "Cookie set with HttpOnly=False - vulnerable to XSS",
                        ));
                    }

                    // Django session cookie settings
                    if let Some(matched) = self.patterns.django_session_insecure.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "django_session_insecure",
                            "Django session cookie security flag disabled",
                        ));
                    }

                    // Django CSRF cookie settings
                    if let Some(matched) = self.patterns.django_csrf_insecure.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "django_csrf_insecure",
                            "Django CSRF cookie security flag disabled",
                        ));
                    }
                }

                Language::JavaScript | Language::TypeScript => {
                    // Express cookie with secure: false
                    if let Some(matched) = self.patterns.js_secure_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_secure",
                            "Cookie set with secure: false - vulnerable to interception over HTTP",
                        ));
                    }

                    // Express cookie with httpOnly: false
                    if let Some(matched) = self.patterns.js_httponly_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_httponly",
                            "Cookie set with httpOnly: false - vulnerable to XSS",
                        ));
                    }
                }

                Language::Go => {
                    // Go http.Cookie with Secure: false
                    if let Some(matched) = self.patterns.go_secure_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_secure",
                            "Cookie set with Secure: false - vulnerable to interception over HTTP",
                        ));
                    }

                    // Go http.Cookie with HttpOnly: false
                    if let Some(matched) = self.patterns.go_httponly_false.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "missing_httponly",
                            "Cookie set with HttpOnly: false - vulnerable to XSS",
                        ));
                    }
                }

                Language::Yaml => {
                    // YAML cookie configuration
                    if let Some(matched) = self.patterns.yaml_cookie_insecure.find(line) {
                        claims.push(Self::make_claim(
                            path_segments,
                            file,
                            line_num,
                            matched.as_str(),
                            "config_insecure",
                            "Cookie security flag disabled in configuration",
                        ));
                    }
                }

                _ => {}
            }

            // SameSite=None check applies to all languages
            // Note: SameSite=None requires Secure flag, otherwise it's rejected
            if let Some(matched) = self.patterns.samesite_none.find(line) {
                if !CookiePatterns::has_secure_flag(line) {
                    claims.push(Self::make_claim(
                        path_segments,
                        file,
                        line_num,
                        matched.as_str(),
                        "samesite_none",
                        "SameSite=None without Secure flag - cookie will be rejected by browsers",
                    ));
                }
            }
        }

        claims
    }
}

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

    fn extractor() -> InsecureCookiesExtractor {
        InsecureCookiesExtractor::new()
    }

    #[test]
    fn test_python_secure_false() {
        let ext = extractor();
        let content = r#"
response.set_cookie("session", value, secure=False)
"#;

        let claims = ext.extract(&["py".to_string()], content, Language::Python, "app.py");

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

    #[test]
    fn test_python_httponly_false() {
        let ext = extractor();
        let content = r#"
response.set_cookie("token", jwt_token, httponly=False)
"#;

        let claims = ext.extract(&["py".to_string()], content, Language::Python, "auth.py");

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

    #[test]
    fn test_django_session_cookie_insecure() {
        let ext = extractor();
        let content = r#"
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = False
"#;

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

        assert_eq!(claims.len(), 2);
        assert!(claims.iter().all(|c| c.concept_path.contains("django_session_insecure")));
    }

    #[test]
    fn test_django_csrf_cookie_insecure() {
        let ext = extractor();
        let content = r#"
CSRF_COOKIE_SECURE = False
"#;

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

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

    #[test]
    fn test_express_secure_false() {
        let ext = extractor();
        let content = r#"
res.cookie('session', token, { secure: false, httpOnly: true });
"#;

        let claims = ext.extract(&["js".to_string()], content, Language::JavaScript, "auth.js");

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

    #[test]
    fn test_express_httponly_false() {
        let ext = extractor();
        let content = r#"
res.cookie('token', jwt, { httpOnly: false, secure: true });
"#;

        let claims =
            ext.extract(&["ts".to_string()], content, Language::TypeScript, "middleware.ts");

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

    #[test]
    fn test_samesite_none_warning() {
        let ext = extractor();
        let content = r#"
res.cookie('cross', value, { sameSite: 'None' });
"#;

        let claims = ext.extract(&["js".to_string()], content, Language::JavaScript, "api.js");

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

    #[test]
    fn test_samesite_none_with_secure_ok() {
        let ext = extractor();
        let content = r#"
res.cookie('cross', value, { sameSite: 'None', secure: true });
"#;

        let claims = ext.extract(&["js".to_string()], content, Language::JavaScript, "api.js");

        // Should NOT flag when secure: true is present
        assert!(claims.is_empty());
    }

    #[test]
    fn test_go_secure_false() {
        let ext = extractor();
        let content = r#"
cookie := &http.Cookie{
    Name:     "session",
    Value:    token,
    Secure:   false,
    HttpOnly: true,
}
"#;

        let claims = ext.extract(&["go".to_string()], content, Language::Go, "auth.go");

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

    #[test]
    fn test_go_httponly_false() {
        let ext = extractor();
        let content = r#"
http.SetCookie(w, &http.Cookie{
    Name:     "token",
    Value:    jwt,
    HttpOnly: false,
})
"#;

        let claims = ext.extract(&["go".to_string()], content, Language::Go, "session.go");

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

    #[test]
    fn test_yaml_cookie_config() {
        let ext = extractor();
        let content = r#"
session:
  cookie_secure: false
  cookie_httponly: no
"#;

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

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

    #[test]
    fn test_secure_cookie_not_flagged() {
        let ext = extractor();
        let content = r#"
response.set_cookie("session", value, secure=True, httponly=True)
"#;

        let claims = ext.extract(&["py".to_string()], content, Language::Python, "app.py");

        assert!(claims.is_empty());
    }

    #[test]
    fn test_httponly_true_not_flagged() {
        let ext = extractor();
        let content = r#"
res.cookie('session', token, { secure: true, httpOnly: true });
"#;

        let claims = ext.extract(&["js".to_string()], content, Language::JavaScript, "auth.js");

        assert!(claims.is_empty());
    }

    #[test]
    fn test_test_file_lower_confidence() {
        let ext = extractor();
        let content = r#"
response.set_cookie("session", value, secure=False)
"#;

        let claims =
            ext.extract(&["test".to_string()], content, Language::Python, "tests/test_cookies.py");

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].confidence, 0.5);
    }
}
