//! Insecure Random Detector
//!
//! Graph-enhanced detection of insecure random:
//! - Trace random values through function calls to security contexts
//! - Check if random is used for IDs, tokens, or passwords
//! - Language-specific secure alternatives

use crate::detectors::base::{Detector, DetectorConfig};
use crate::graph::GraphStore;
use crate::models::{deterministic_finding_id, Finding, Severity};
use anyhow::Result;
use regex::Regex;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::OnceLock;
use tracing::info;
use uuid::Uuid;

static INSECURE_RANDOM: OnceLock<Regex> = OnceLock::new();

fn insecure_random() -> &'static Regex {
    INSECURE_RANDOM.get_or_init(|| {
        Regex::new(r"(?i)(Math\.random\(\)|random\.random\(\)|random\.randint|rand\(\)|srand\(|mt_rand|lcg_value|uniqid)").unwrap()
    })
}

/// Get secure alternative for each language
fn get_secure_alternative(ext: &str) -> &'static str {
    match ext {
        "py" => {
            "```python\n\
                 import secrets\n\
                 \n\
                 # For tokens/passwords\n\
                 token = secrets.token_urlsafe(32)\n\
                 \n\
                 # For random integers\n\
                 num = secrets.randbelow(100)\n\
                 \n\
                 # For random bytes\n\
                 data = secrets.token_bytes(16)\n\
                 ```"
        }
        "js" | "ts" => {
            "```javascript\n\
                        // Node.js\n\
                        const crypto = require('crypto');\n\
                        const token = crypto.randomBytes(32).toString('hex');\n\
                        \n\
                        // Browser\n\
                        const array = new Uint8Array(32);\n\
                        crypto.getRandomValues(array);\n\
                        ```"
        }
        "java" => {
            "```java\n\
                   import java.security.SecureRandom;\n\
                   \n\
                   SecureRandom random = new SecureRandom();\n\
                   byte[] bytes = new byte[32];\n\
                   random.nextBytes(bytes);\n\
                   ```"
        }
        "go" => {
            "```go\n\
                 import \"crypto/rand\"\n\
                 \n\
                 bytes := make([]byte, 32)\n\
                 rand.Read(bytes)\n\
                 ```"
        }
        "php" => {
            "```php\n\
                  // PHP 7+\n\
                  $bytes = random_bytes(32);\n\
                  $token = bin2hex($bytes);\n\
                  ```"
        }
        "rb" => {
            "```ruby\n\
                 require 'securerandom'\n\
                 \n\
                 token = SecureRandom.hex(32)\n\
                 ```"
        }
        "c" | "cpp" => {
            "```c\n\
                        // Linux\n\
                        #include <sys/random.h>\n\
                        getrandom(buffer, size, 0);\n\
                        \n\
                        // Or read from /dev/urandom\n\
                        ```"
        }
        _ => "Use your platform's cryptographic random number generator.",
    }
}

pub struct InsecureRandomDetector {
    repository_path: PathBuf,
    max_findings: usize,
}

impl InsecureRandomDetector {
    pub fn new(repository_path: impl Into<PathBuf>) -> Self {
        Self {
            repository_path: repository_path.into(),
            max_findings: 50,
        }
    }

    /// Check what the random value is used for
    fn analyze_usage(line: &str, surrounding: &str) -> (SecurityContext, String) {
        let combined = format!("{} {}", line, surrounding).to_lowercase();

        // Token/secret generation
        if combined.contains("token") || combined.contains("secret") || combined.contains("api_key")
        {
            return (
                SecurityContext::Token,
                "token/secret generation".to_string(),
            );
        }

        // Password/salt
        if combined.contains("password") || combined.contains("salt") || combined.contains("hash") {
            return (
                SecurityContext::Password,
                "password/salt generation".to_string(),
            );
        }

        // Session/auth
        if combined.contains("session") || combined.contains("auth") || combined.contains("login") {
            return (
                SecurityContext::Session,
                "session/authentication".to_string(),
            );
        }

        // ID generation
        if combined.contains("id") || combined.contains("uuid") || combined.contains("identifier") {
            return (SecurityContext::ID, "ID generation".to_string());
        }

        // Crypto
        if combined.contains("crypto")
            || combined.contains("encrypt")
            || combined.contains("key")
            || combined.contains("iv")
            || combined.contains("nonce")
        {
            return (
                SecurityContext::Crypto,
                "cryptographic operation".to_string(),
            );
        }

        // OTP/verification
        if combined.contains("otp")
            || combined.contains("code")
            || combined.contains("verification")
            || combined.contains("pin")
        {
            return (SecurityContext::OTP, "OTP/verification code".to_string());
        }

        (SecurityContext::Unknown, "unknown".to_string())
    }

    /// Find functions that use insecure random and are called by security-related code
    fn find_security_callers(&self, graph: &dyn crate::graph::GraphQuery, func_name: &str) -> Vec<String> {
        let mut security_callers = Vec::new();

        if let Some(func) = graph
            .get_functions()
            .into_iter()
            .find(|f| f.name == func_name)
        {
            let callers = graph.get_callers(&func.qualified_name);

            for caller in callers {
                let caller_lower = caller.name.to_lowercase();
                if caller_lower.contains("auth")
                    || caller_lower.contains("login")
                    || caller_lower.contains("token")
                    || caller_lower.contains("session")
                    || caller_lower.contains("password")
                    || caller_lower.contains("secret")
                {
                    security_callers.push(caller.name.clone());
                }
            }
        }

        security_callers
    }

    /// Find containing function
    fn find_containing_function(graph: &dyn crate::graph::GraphQuery, file_path: &str, line: u32) -> Option<String> {
        graph
            .get_functions()
            .into_iter()
            .find(|f| f.file_path == file_path && f.line_start <= line && f.line_end >= line)
            .map(|f| f.name)
    }
}

#[derive(PartialEq)]
enum SecurityContext {
    Token,
    Password,
    Session,
    ID,
    Crypto,
    OTP,
    Unknown,
}

impl Detector for InsecureRandomDetector {
    fn name(&self) -> &'static str {
        "insecure-random"
    }
    fn description(&self) -> &'static str {
        "Detects insecure random for security purposes"
    }

    fn detect(&self, graph: &dyn crate::graph::GraphQuery) -> Result<Vec<Finding>> {
        let mut findings = vec![];
        let walker = ignore::WalkBuilder::new(&self.repository_path)
            .hidden(false)
            .git_ignore(true)
            .build();

        for entry in walker.filter_map(|e| e.ok()) {
            if findings.len() >= self.max_findings {
                break;
            }
            let path = entry.path();
            if !path.is_file() {
                continue;
            }

            let path_str = path.to_string_lossy().to_string();

            // Skip test files
            if path_str.contains("test") {
                continue;
            }

            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
            if !matches!(
                ext,
                "py" | "js" | "ts" | "java" | "go" | "rb" | "php" | "c" | "cpp"
            ) {
                continue;
            }

            if let Some(content) = crate::cache::global_cache().get_content(path) {
                let lines: Vec<&str> = content.lines().collect();

                for (i, line) in lines.iter().enumerate() {
                    if insecure_random().is_match(line) {
                        let start = i.saturating_sub(5);
                        let end = (i + 5).min(lines.len());
                        let surrounding = lines[start..end].join(" ");

                        let (context, usage) = Self::analyze_usage(line, &surrounding);
                        let containing_func =
                            Self::find_containing_function(graph, &path_str, (i + 1) as u32);

                        // Check if function is called by security code
                        let security_callers = if let Some(ref func) = containing_func {
                            self.find_security_callers(graph, func)
                        } else {
                            vec![]
                        };

                        // Only flag if in security context
                        if context == SecurityContext::Unknown && security_callers.is_empty() {
                            continue;
                        }

                        // Calculate severity
                        let severity = match context {
                            SecurityContext::Crypto | SecurityContext::Password => {
                                Severity::Critical
                            }
                            SecurityContext::Token
                            | SecurityContext::Session
                            | SecurityContext::OTP => Severity::High,
                            SecurityContext::ID => Severity::Medium,
                            SecurityContext::Unknown if !security_callers.is_empty() => {
                                Severity::High
                            }
                            _ => Severity::Medium,
                        };

                        // Build notes
                        let mut notes = Vec::new();
                        notes.push(format!("🎯 Used for: {}", usage));
                        if let Some(func) = &containing_func {
                            notes.push(format!("📦 In function: `{}`", func));
                        }
                        if !security_callers.is_empty() {
                            notes.push(format!(
                                "⚠️ Called by security functions: {}",
                                security_callers.join(", ")
                            ));
                        }

                        let context_notes = format!("\n\n**Analysis:**\n{}", notes.join("\n"));

                        let random_func = insecure_random()
                            .find(line)
                            .map(|m| m.as_str())
                            .unwrap_or("random");

                        findings.push(Finding {
                            id: Uuid::new_v4().to_string(),
                            detector: "InsecureRandomDetector".to_string(),
                            severity,
                            title: format!("Insecure `{}` used for {}", random_func, usage),
                            description: format!(
                                "`{}` is not cryptographically secure and can be predicted by attackers.{}",
                                random_func, context_notes
                            ),
                            affected_files: vec![path.to_path_buf()],
                            line_start: Some((i + 1) as u32),
                            line_end: Some((i + 1) as u32),
                            suggested_fix: Some(format!(
                                "Use a cryptographically secure random number generator:\n\n{}",
                                get_secure_alternative(ext)
                            )),
                            estimated_effort: Some("15 minutes".to_string()),
                            category: Some("security".to_string()),
                            cwe_id: Some("CWE-330".to_string()),
                            why_it_matters: Some(
                                "Insecure random number generators (like Math.random or random.random) \
                                 use predictable algorithms. Attackers can often guess the output and \
                                 forge tokens, guess passwords, or bypass authentication.".to_string()
                            ),
                            ..Default::default()
                        });
                    }
                }
            }
        }

        info!(
            "InsecureRandomDetector found {} findings (graph-aware)",
            findings.len()
        );
        Ok(findings)
    }
}
