//! Pattern matching and false positive detection for secret strings.

use regex::Regex;

/// Build regex patterns for known secret prefixes and contexts.
pub struct SecretPatterns {
    /// Known secret prefixes (high confidence, no entropy check needed).
    /// Matches: sk_live_*, sk_test_*, AKIA*, ghp_*, gho_*, glpat-*, xox[baprs]-*
    pub known_prefixes: Regex,

    /// High-entropy contexts (requires entropy + charset check).
    /// Matches: api_key = "...", secret: "...", token = "...", etc.
    pub secret_context: Regex,

    /// Generic env var assignment patterns for secrets.
    pub env_var_secret: Regex,
}

impl SecretPatterns {
    /// Create new secret patterns.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Known secret prefixes - these are high confidence without entropy check
            // - sk_live_*, sk_test_*: Stripe API keys
            // - AKIA*: AWS Access Key IDs (exactly 20 chars after AKIA)
            // - ghp_*, gho_*: GitHub PAT and OAuth tokens
            // - glpat-*: GitLab PATs
            // - xox[baprs]-*: Slack tokens (bot, app, user, etc.)
            known_prefixes: Regex::new(
                r"(?:sk_(?:live|test)_[A-Za-z0-9]{24,}|AKIA[0-9A-Z]{16}|gh[po]_[A-Za-z0-9]{36}|glpat-[A-Za-z0-9\-]{20,}|xox[baprs]-[A-Za-z0-9\-]{10,})"
            ).expect("valid regex"),

            // Context patterns for secrets - capture the secret value
            secret_context: Regex::new(
                r#"(?i)(?:api[_-]?key|secret[_-]?key|auth[_-]?key|access[_-]?token|private[_-]?key|credential|bearer)\s*[:=]\s*["']?([A-Za-z0-9+/=_\-]{20,})["']?"#
            ).expect("valid regex"),

            // Environment variable patterns for secrets
            env_var_secret: Regex::new(
                r#"(?i)(?:^|\s)([A-Z_]+(?:API[_-]?KEY|SECRET|TOKEN|CREDENTIAL|AUTH[_-]?KEY))\s*[:=]\s*["']?([A-Za-z0-9+/=_\-]{20,})["']?"#
            ).expect("valid regex"),
        }
    }
}

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

/// Check if a string is likely NOT a secret (false positive exclusion).
pub fn is_likely_not_secret(s: &str) -> bool {
    // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    let is_uuid = s.len() == 36
        && s.chars().filter(|&c| c == '-').count() == 4
        && s.chars().all(|c| c.is_ascii_hexdigit() || c == '-');

    // Git SHA (40-char hex)
    let is_git_sha = s.len() == 40 && s.chars().all(|c| c.is_ascii_hexdigit());

    // MD5 hash (32-char hex)
    let is_md5_hash = s.len() == 32 && s.chars().all(|c| c.is_ascii_hexdigit());

    // File hash (64-char hex, SHA256)
    let is_file_hash = s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit());

    // Base64-encoded URLs often start with "aHR0c" (http) or "ZmlsZ" (file)
    let is_likely_base64_url = s.starts_with("aHR0c") || s.starts_with("ZmlsZ");

    // Common placeholder patterns
    let is_placeholder = {
        let lower = s.to_lowercase();
        lower.contains("example")
            || lower.contains("placeholder")
            || lower.contains("changeme")
            || lower.contains("your_")
            || lower.contains("xxx")
    };

    is_uuid || is_git_sha || is_md5_hash || is_file_hash || is_likely_base64_url || is_placeholder
}

/// Determine secret type and description from a matched prefix.
pub fn classify_known_secret(matched_str: &str) -> (&'static str, &'static str) {
    if matched_str.starts_with("sk_live_") {
        ("stripe_live_key", "Stripe live API key detected")
    } else if matched_str.starts_with("sk_test_") {
        ("stripe_test_key", "Stripe test API key detected")
    } else if matched_str.starts_with("AKIA") {
        ("aws_access_key", "AWS Access Key ID detected")
    } else if matched_str.starts_with("ghp_") {
        ("github_pat", "GitHub Personal Access Token detected")
    } else if matched_str.starts_with("gho_") {
        ("github_oauth", "GitHub OAuth token detected")
    } else if matched_str.starts_with("glpat-") {
        ("gitlab_pat", "GitLab Personal Access Token detected")
    } else if matched_str.starts_with("xox") {
        ("slack_token", "Slack API token detected")
    } else {
        ("known_secret", "Known secret prefix detected")
    }
}
