//! Redaction policy: the catalogue of patterns and structural rules
//! the redactor applies.

use once_cell::sync::Lazy;
use regex::Regex;

/// A single regex-driven rule.
#[derive(Debug, Clone)]
pub struct RedactionRule {
    pub id: &'static str,
    pub description: &'static str,
    pub pattern: Regex,
    /// If `Some(n)`, the rule replaces capture-group `n` rather than
    /// the whole match. Useful for "ts3server://host?password=XXXX"
    /// where the host should be preserved but `XXXX` redacted.
    pub redact_group: Option<usize>,
}

impl RedactionRule {
    pub fn new(
        id: &'static str,
        description: &'static str,
        pattern: &str,
    ) -> Self {
        Self {
            id,
            description,
            pattern: Regex::new(pattern).expect("static rule regex must compile"),
            redact_group: None,
        }
    }

    pub fn new_group(
        id: &'static str,
        description: &'static str,
        pattern: &str,
        group: usize,
    ) -> Self {
        Self {
            id,
            description,
            pattern: Regex::new(pattern).expect("static rule regex must compile"),
            redact_group: Some(group),
        }
    }
}

/// The full redaction policy.
#[derive(Debug, Clone)]
pub struct RedactionPolicy {
    pub rules: Vec<RedactionRule>,

    /// Structured-field redaction: any key whose lower-cased name
    /// contains one of these substrings is fully redacted in
    /// diagnostic bundles. Mirrors the bundle policy in
    /// `diagnostic-redaction-audit-report.md` §5.
    pub redact_field_substrings: Vec<&'static str>,

    /// Bundle-level: include chat messages? Audit-report default = No.
    pub include_chat: bool,

    /// Bundle-level: include channel tree details?
    pub include_channel_tree: bool,
}

/// Hard upper bound on protocol-string lengths kept in diagnostics.
///
/// REDACT-TC-009 ("long hostile protocol string"): truncate or safely
/// escape. The PoC truncates with a clear marker.
pub const MAX_PROTOCOL_STRING_LEN: usize = 256;

impl RedactionPolicy {
    /// The default policy used by the PoC. Mirrors the audit-report
    /// catalogue. Production code (`chanora_diagnostics`) will own the
    /// canonical version of this list.
    pub fn default_policy() -> Self {
        Self {
            rules: DEFAULT_RULES.clone(),
            redact_field_substrings: vec![
                "password",
                "secret",
                "private_key",
                "private-key",
                "identity_key",
                "identity-key",
                "token",
                "credential",
                "passphrase",
            ],
            include_chat: false,
            include_channel_tree: false,
        }
    }
}

static DEFAULT_RULES: Lazy<Vec<RedactionRule>> = Lazy::new(|| {
    vec![
        // Identity secret as it appears in `tsclientlib` style:
        // long base64-ish run preceded by a known marker.
        // We err on the side of catching base64 blobs >= 64 chars.
        RedactionRule::new(
            "identity-base64-blob",
            "Long base64-like run; matches identity secrets and tokens.",
            r"\b[A-Za-z0-9+/]{64,}={0,2}\b",
        ),
        // `password = "..."` / `password: "..."` / `password=...`
        // Captures the value in group 1 so the *key* name is kept.
        // The value excludes separators commonly found in URL query
        // strings and config lines (`& , ; " whitespace`).
        RedactionRule::new_group(
            "password-kv",
            "key=value style password assignment.",
            r#"(?i)\b(?:password|passwd|pass|pwd)\s*[:=]\s*"?([^"\s,;&]+)"?"#,
            1,
        ),
        // ts3server://host?password=XXXX&...
        RedactionRule::new_group(
            "ts3server-url-password",
            "Password embedded in a ts3server:// URL query string.",
            r"(?i)(?:[?&])password=([^&\s]+)",
            1,
        ),
        // Generic Authorization: Bearer ...
        RedactionRule::new_group(
            "authorization-bearer",
            "Authorization header bearer token.",
            r"(?i)Authorization:\s*Bearer\s+(\S+)",
            1,
        ),
        // Linux user home: /home/<user>/... → minimize the username
        // segment. Captures group 1 = username.
        RedactionRule::new_group(
            "linux-home-path",
            "Linux home-directory path; minimizes the username segment.",
            r"(/home/)([^/\s]+)",
            2,
        ),
        // Windows user: C:\Users\<user>\...
        RedactionRule::new_group(
            "windows-user-path",
            "Windows user-profile path; minimizes the username segment.",
            r"(?i)([A-Z]:\\Users\\)([^\\\s]+)",
            2,
        ),
        // macOS user: /Users/<user>/...
        RedactionRule::new_group(
            "macos-user-path",
            "macOS user-profile path; minimizes the username segment.",
            r"(/Users/)([^/\s]+)",
            2,
        ),
    ]
});
