//! Authentication bypass extractor.
//!
//! Detects patterns that could indicate authentication bypass vulnerabilities:
//! - Hardcoded admin credentials
//! - Debug auth headers
//! - Skip auth environment variables
//! - Backdoor patterns
//!
//! These are critical security vulnerabilities that can lead to unauthorized access.

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

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

/// Extractor for authentication bypass patterns.
///
/// Detects hardcoded credentials, debug auth headers, and backdoor patterns
/// that could allow attackers to bypass authentication.
pub struct AuthBypassExtractor {
    /// Hardcoded admin credentials (username == "admin" && password == "...")
    hardcoded_admin_creds: Regex,

    /// Debug auth headers (X-Debug-Auth, X-Internal-Auth, X-Admin-Auth)
    debug_auth_header: Regex,

    /// Skip auth environment variables (SKIP_AUTH, BYPASS_AUTH, NO_AUTH)
    skip_auth_env: Regex,

    /// Backdoor patterns (if username == "backdoor"/"admin"/"root")
    backdoor_pattern: Regex,

    /// Default/test credentials in production context
    default_creds: Regex,
}

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

impl AuthBypassExtractor {
    /// Create a new auth bypass extractor with compiled regexes.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Hardcoded admin credentials patterns
            // Matches: username == "admin" && password == "secret"
            // Matches: user === 'admin' && pwd === 'pass' (JavaScript strict equality)
            // Matches: user == 'admin' and pwd == 'pass'
            hardcoded_admin_creds: Regex::new(
                r#"(?i)(?:username|user|login)\s*={1,3}\s*["'](?:admin|administrator|root)["']\s*(?:&&|and|\|\|)\s*(?:password|pass|pwd)\s*={1,3}\s*["'][^"']+["']"#
            ).expect("valid regex"),

            // Debug auth headers
            // Matches: headers.get("X-Debug-Auth"), request.headers("X-Internal-Auth")
            debug_auth_header: Regex::new(
                r#"(?i)(?:headers?\.get|request\.headers?|get_header|Header)\s*\(\s*["'](X-Debug-Auth|X-Internal-Auth|X-Admin-Auth|X-Backdoor|X-Test-Auth|X-Dev-Auth)["']\s*\)"#
            ).expect("valid regex"),

            // Skip auth environment variables
            // Matches: SKIP_AUTH == "true", NO_AUTH=1, BYPASS_AUTH != ""
            skip_auth_env: Regex::new(
                r#"(?i)(?:os\.(?:getenv|environ)|env::var|process\.env|Getenv)\s*\(\s*["']?(SKIP_AUTH|BYPASS_AUTH|NO_AUTH|DEBUG_AUTH|DISABLE_AUTH)["']?\s*\)"#
            ).expect("valid regex"),

            // Backdoor patterns
            // Matches: if username == "backdoor", if user == "master"
            backdoor_pattern: Regex::new(
                r#"(?i)if\s*\(?\s*(?:username|user|login|email)\s*==?\s*["'](backdoor|master|superuser|god|debug|test_admin)["']"#
            ).expect("valid regex"),

            // Default/test credentials in production-looking context
            // Matches: password = "admin123", auth_token = "test"
            default_creds: Regex::new(
                r#"(?i)(?:password|passwd|pwd|auth_token|api_key)\s*[:=]\s*["'](admin|admin123|password|password123|test|testing|default|changeme|secret)["']"#
            ).expect("valid regex"),
        }
    }

    fn make_claim(
        path_segments: &[String],
        file: &str,
        line: usize,
        matched_text: &str,
        bypass_type: &str,
        description: &str,
    ) -> ExtractedClaim {
        build_claim(
            path_segments,
            &["auth", "bypass", bypass_type],
            "auth_bypass_pattern",
            ObjectValue::Text(bypass_type.to_string()),
            file,
            line,
            matched_text,
            1.0,
            description,
        )
    }
}

impl Extractor for AuthBypassExtractor {
    fn name(&self) -> &str {
        "auth_bypass"
    }

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

    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;

            // Hardcoded admin credentials
            if let Some(matched) = self.hardcoded_admin_creds.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "hardcoded_admin_creds",
                    "Hardcoded admin credentials detected - critical auth bypass vulnerability",
                ));
            }

            // Debug auth headers
            if let Some(matched) = self.debug_auth_header.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "debug_auth_header",
                    "Debug authentication header detected - potential backdoor",
                ));
            }

            // Skip auth env vars
            if let Some(matched) = self.skip_auth_env.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "skip_auth_env_var",
                    "Auth bypass environment variable detected - ensure not used in production",
                ));
            }

            // Backdoor patterns
            if let Some(matched) = self.backdoor_pattern.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "backdoor_pattern",
                    "Potential backdoor user check detected",
                ));
            }

            // Default credentials
            if let Some(matched) = self.default_creds.find(line) {
                claims.push(Self::make_claim(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "default_credentials",
                    "Default/test credentials detected in code",
                ));
            }
        }

        claims
    }
}

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

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

    #[test]
    fn test_hardcoded_admin_creds_python() {
        let ext = extractor();
        let content = r#"
if username == "admin" and password == "secret123":
    return True
"#;

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

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

    #[test]
    fn test_hardcoded_admin_creds_js() {
        let ext = extractor();
        let content = r#"
if (user === "administrator" && pwd === "admin123") {
    return true;
}
"#;

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

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

    #[test]
    fn test_debug_auth_header_python() {
        let ext = extractor();
        let content = r#"
debug_token = request.headers.get("X-Debug-Auth")
if debug_token:
    return authenticate_debug()
"#;

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

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

    #[test]
    fn test_debug_auth_header_go() {
        let ext = extractor();
        let content = r#"
func authMiddleware(r *http.Request) {
    if debugAuth := r.Header.Get("X-Internal-Auth"); debugAuth != "" {
        // bypass
    }
}
"#;

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

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

    #[test]
    fn test_skip_auth_env_var_python() {
        let ext = extractor();
        let content = r#"
if os.getenv("SKIP_AUTH"):
    return True
"#;

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

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

    #[test]
    fn test_skip_auth_env_var_go() {
        let ext = extractor();
        let content = r#"
if os.Getenv("BYPASS_AUTH") == "true" {
    return nil
}
"#;

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

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

    #[test]
    fn test_skip_auth_env_var_rust() {
        let ext = extractor();
        let content = r#"
if std::env::var("NO_AUTH").is_ok() {
    return Ok(());
}
"#;

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

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

    #[test]
    fn test_backdoor_pattern() {
        let ext = extractor();
        let content = r#"
if (username == "backdoor") {
    grantAdminAccess();
}
"#;

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

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

    #[test]
    fn test_default_credentials() {
        let ext = extractor();
        let content = r#"
const DEFAULT_PASSWORD = "admin123";
"#;

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

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

    #[test]
    fn test_normal_auth_check_not_flagged() {
        let ext = extractor();
        let content = r#"
def authenticate(username, password):
    user = db.get_user(username)
    if user and user.verify_password(password):
        return True
    return False
"#;

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

        // Normal auth check should not be flagged
        assert!(claims.is_empty());
    }

    #[test]
    fn test_env_var_check_not_flagged() {
        let ext = extractor();
        let content = r#"
# Normal env var usage for configuration
database_url = os.getenv("DATABASE_URL")
api_endpoint = os.environ.get("API_ENDPOINT")
"#;

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

        // Normal env var usage should not be flagged
        assert!(claims.is_empty());
    }

    #[test]
    fn test_test_file_lower_confidence() {
        let ext = extractor();
        let content = r#"
if username == "admin" and password == "test123":
    return True
"#;

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

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

    #[test]
    fn test_multiple_patterns_same_file() {
        let ext = extractor();
        let content = r#"
def authenticate(request):
    # Backdoor for debugging
    if os.getenv("DEBUG_AUTH"):
        return True

    debug_token = request.headers.get("X-Debug-Auth")
    if debug_token == "secret":
        return True

    # Admin override
    if request.user == "admin" and request.password == "admin123":
        return True

    return verify_credentials(request)
"#;

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

        // Should find multiple issues: skip_auth, debug_header, hardcoded_admin
        assert!(claims.len() >= 2);
    }
}
