//! Content redaction for embedded files.
//!
//! This module implements pattern-based redaction to prevent secrets from
//! being included in embedded file content. Redaction only affects the
//! payload sent to the agent - files on disk are never modified.

use regex::Regex;

use crate::error::ToolResult;

/// A single redaction pattern with name and replacement text.
#[derive(Debug, Clone)]
pub struct RedactionPattern {
    /// Human-readable name for this pattern (e.g., "API_KEY").
    pub name: String,
    /// Compiled regular expression to match.
    pub regex: Regex,
    /// Replacement text (e.g., "<REDACTED:API_KEY>").
    pub replacement: String,
}

impl RedactionPattern {
    /// Create a new redaction pattern.
    ///
    /// # Arguments
    ///
    /// * `name` - Human-readable name
    /// * `pattern` - Regular expression pattern
    /// * `replacement` - Replacement text (can include pattern name)
    ///
    /// # Returns
    ///
    /// A compiled redaction pattern, or an error if the regex is invalid.
    pub fn new(
        name: impl Into<String>,
        pattern: &str,
        replacement: impl Into<String>,
    ) -> ToolResult<Self> {
        let name = name.into();
        let regex = Regex::new(pattern).map_err(|e| {
            crate::error::ToolError::InvalidInput(format!("Invalid redaction pattern: {}", e))
        })?;

        Ok(Self {
            name,
            regex,
            replacement: replacement.into(),
        })
    }

    /// Create a redaction pattern with default "<REDACTED:NAME>" replacement.
    pub fn with_default_replacement(name: impl Into<String>, pattern: &str) -> ToolResult<Self> {
        let name_str = name.into();
        let replacement = format!("<REDACTED:{}>", name_str.to_uppercase());
        Self::new(name_str, pattern, replacement)
    }
}

/// Content redactor for secret patterns.
///
/// Applies a set of redaction patterns to content before embedding.
pub struct ContentRedactor {
    /// Redaction patterns to apply.
    patterns: Vec<RedactionPattern>,
}

impl ContentRedactor {
    /// Create a new content redactor with the given patterns.
    ///
    /// # Arguments
    ///
    /// * `pattern_strings` - List of regex pattern strings from configuration
    ///
    /// # Returns
    ///
    /// A content redactor, or an error if any pattern is invalid.
    pub fn new(pattern_strings: &[String]) -> ToolResult<Self> {
        let mut patterns = Vec::new();

        for (i, pattern_str) in pattern_strings.iter().enumerate() {
            let pattern = RedactionPattern::with_default_replacement(
                format!("CUSTOM_{}", i),
                pattern_str,
            )?;
            patterns.push(pattern);
        }

        Ok(Self { patterns })
    }

    /// Create a redactor with default built-in patterns.
    ///
    /// Default patterns include:
    /// - API keys
    /// - AWS credentials
    /// - Generic secrets and tokens
    /// - Passwords
    pub fn with_default_patterns() -> Self {
        let mut patterns = Vec::new();

        // API keys (various formats)
        if let Ok(p) = RedactionPattern::with_default_replacement(
            "API_KEY",
            r#"(?i)(api[_-]?key|apikey)[:=\s]+["']?([a-zA-Z0-9_\-\.]{20,})["']?"#,
        ) {
            patterns.push(p);
        }

        // AWS access key IDs
        if let Ok(p) = RedactionPattern::with_default_replacement(
            "AWS_ACCESS_KEY",
            r"AKIA[0-9A-Z]{16}",
        ) {
            patterns.push(p);
        }

        // Generic secrets and tokens
        if let Ok(p) = RedactionPattern::with_default_replacement(
            "SECRET",
            r#"(?i)(secret|token)[:=\s]+["']?([a-zA-Z0-9_\-\.]{16,})["']?"#,
        ) {
            patterns.push(p);
        }

        // Passwords
        if let Ok(p) = RedactionPattern::with_default_replacement(
            "PASSWORD",
            r#"(?i)password[:=\s]+["']?([^\s"']{8,})["']?"#,
        ) {
            patterns.push(p);
        }

        // Bearer tokens
        if let Ok(p) = RedactionPattern::with_default_replacement(
            "BEARER_TOKEN",
            r"(?i)bearer\s+([a-zA-Z0-9_\-\.=]+)",
        ) {
            patterns.push(p);
        }

        Self { patterns }
    }

    /// Create a redactor combining default and custom patterns.
    ///
    /// # Arguments
    ///
    /// * `custom_patterns` - Additional custom pattern strings
    pub fn with_custom_patterns(custom_patterns: &[String]) -> ToolResult<Self> {
        let mut redactor = Self::with_default_patterns();

        for (i, pattern_str) in custom_patterns.iter().enumerate() {
            let pattern = RedactionPattern::with_default_replacement(
                format!("CUSTOM_{}", i),
                pattern_str,
            )?;
            redactor.patterns.push(pattern);
        }

        Ok(redactor)
    }

    /// Redact sensitive content from the given text.
    ///
    /// Applies all configured redaction patterns in order and returns
    /// the redacted content. The original content is never modified.
    ///
    /// # Arguments
    ///
    /// * `content` - The content to redact
    ///
    /// # Returns
    ///
    /// Redacted content with sensitive data replaced.
    pub fn redact(&self, content: &str) -> String {
        let mut redacted = content.to_string();

        for pattern in &self.patterns {
            redacted = pattern.regex.replace_all(&redacted, &pattern.replacement).to_string();
        }

        // Ensure UTF-8 safety (should always be valid since we're only replacing with ASCII)
        debug_assert!(redacted.is_char_boundary(redacted.len()));

        redacted
    }

    /// Check if content contains any patterns that would be redacted.
    ///
    /// This can be used to warn users before embedding.
    pub fn contains_secrets(&self, content: &str) -> bool {
        self.patterns.iter().any(|p| p.regex.is_match(content))
    }

    /// Get the number of redaction patterns configured.
    pub fn pattern_count(&self) -> usize {
        self.patterns.len()
    }
}

// Implement a safe Debug that doesn't leak pattern details
impl std::fmt::Debug for ContentRedactor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ContentRedactor")
            .field("pattern_count", &self.patterns.len())
            .finish()
    }
}

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

    #[test]
    fn test_redaction_pattern_creation() {
        let pattern = RedactionPattern::new("TEST", r"\d+", "XXX").unwrap();

        assert_eq!(pattern.name, "TEST");
        assert_eq!(pattern.replacement, "XXX");
        assert!(pattern.regex.is_match("123"));
    }

    #[test]
    fn test_redaction_pattern_with_default_replacement() {
        let pattern = RedactionPattern::with_default_replacement("api_key", r"key_\d+").unwrap();

        assert_eq!(pattern.name, "api_key");
        assert_eq!(pattern.replacement, "<REDACTED:API_KEY>");
    }

    #[test]
    fn test_redaction_pattern_invalid_regex() {
        let result = RedactionPattern::new("TEST", r"[invalid(", "XXX");

        assert!(result.is_err());
    }

    #[test]
    fn test_default_patterns_api_key() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "API_KEY=sk_live_1234567890abcdefghij";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("sk_live_"));
    }

    #[test]
    fn test_default_patterns_aws_key() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("AKIAIOSFODNN7EXAMPLE"));
    }

    #[test]
    fn test_default_patterns_secret() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "secret: my_super_secret_value_12345";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("my_super_secret"));
    }

    #[test]
    fn test_default_patterns_password() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "password=MyP@ssw0rd123";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("MyP@ssw0rd"));
    }

    #[test]
    fn test_default_patterns_bearer_token() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("eyJhbGci"));
    }

    #[test]
    fn test_custom_patterns() {
        let custom = vec![r"CUSTOM-\d{4}".to_string()];
        let redactor = ContentRedactor::with_custom_patterns(&custom).unwrap();

        let content = "My custom ID is CUSTOM-1234";
        let redacted = redactor.redact(content);

        assert!(redacted.contains("<REDACTED:"));
        assert!(!redacted.contains("CUSTOM-1234"));
    }

    #[test]
    fn test_multiple_patterns() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = r#"
            API_KEY="sk_test_abcdefghijklmnopqrst"
            password=secret123
            token: my_long_secret_token_value
        "#;

        let redacted = redactor.redact(content);

        // All secrets should be redacted
        assert!(!redacted.contains("sk_test_"));
        assert!(!redacted.contains("secret123"));
        assert!(!redacted.contains("my_long_secret"));
        assert!(redacted.contains("<REDACTED:"));
    }

    #[test]
    fn test_contains_secrets() {
        let redactor = ContentRedactor::with_default_patterns();

        assert!(redactor.contains_secrets("API_KEY=sk_test_12345678901234567890"));
        assert!(redactor.contains_secrets("password=secret"));
        assert!(!redactor.contains_secrets("Hello, world!"));
    }

    #[test]
    fn test_utf8_safety() {
        let redactor = ContentRedactor::with_default_patterns();

        let content = "API_KEY=sk_test_12345678901234567890 你好世界";
        let redacted = redactor.redact(content);

        // Should still contain valid UTF-8
        assert!(redacted.contains("你好世界"));
        assert!(std::str::from_utf8(redacted.as_bytes()).is_ok());
    }

    #[test]
    fn test_no_false_positives_on_code() {
        let redactor = ContentRedactor::with_default_patterns();

        let code = r#"
            fn api_key_validator(input: &str) -> bool {
                input.len() > 20
            }
        "#;

        let redacted = redactor.redact(code);

        // Function name and code should not be redacted
        assert!(redacted.contains("api_key_validator"));
        assert!(redacted.contains("fn"));
    }

    #[test]
    fn test_pattern_count() {
        let redactor = ContentRedactor::with_default_patterns();
        assert_eq!(redactor.pattern_count(), 5); // 5 default patterns

        let custom = vec![r"CUSTOM-\d{4}".to_string()];
        let redactor_custom = ContentRedactor::with_custom_patterns(&custom).unwrap();
        assert_eq!(redactor_custom.pattern_count(), 6); // 5 default + 1 custom
    }

    #[test]
    fn test_debug_impl_safe() {
        let redactor = ContentRedactor::with_default_patterns();
        let debug_str = format!("{:?}", redactor);

        // Should not leak actual pattern details
        assert!(debug_str.contains("ContentRedactor"));
        assert!(debug_str.contains("pattern_count"));
        assert!(!debug_str.contains("regex")); // Internal details hidden
    }
}
