//! The redactor — applies the policy to free text and to structured
//! diagnostic data.

use std::collections::HashSet;
use std::sync::{Arc, RwLock};

use crate::policy::{RedactionPolicy, MAX_PROTOCOL_STRING_LEN};
use crate::REDACTION_MARKER;

/// Runtime registry of *literal* known secrets. The host application
/// (e.g. the secure-storage layer) registers a secret here when it
/// loads one into memory, so even if it slips into a log line verbatim
/// it gets scrubbed.
///
/// This is the strongest defense for SS-AUD-003 and REDACT-TC-002 —
/// regexes can miss; literal substring matches cannot.
#[derive(Debug, Clone, Default)]
pub struct KnownSecretRegistry {
    inner: Arc<RwLock<HashSet<String>>>,
}

impl KnownSecretRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a literal secret string. Empty strings are ignored
    /// (to avoid degenerate `s.replace("", _)` behaviour).
    pub fn register(&self, secret: &str) {
        if secret.is_empty() {
            return;
        }
        self.inner.write().unwrap().insert(secret.to_string());
    }

    pub fn forget(&self, secret: &str) {
        self.inner.write().unwrap().remove(secret);
    }

    pub fn snapshot(&self) -> Vec<String> {
        self.inner.read().unwrap().iter().cloned().collect()
    }
}

pub struct Redactor {
    policy: RedactionPolicy,
    known: KnownSecretRegistry,
}

impl Redactor {
    pub fn new(policy: RedactionPolicy, known: KnownSecretRegistry) -> Self {
        Self { policy, known }
    }

    pub fn with_default_policy() -> Self {
        Self::new(RedactionPolicy::default_policy(), KnownSecretRegistry::new())
    }

    pub fn known_secrets(&self) -> &KnownSecretRegistry {
        &self.known
    }

    pub fn policy(&self) -> &RedactionPolicy {
        &self.policy
    }

    /// Redact `input` according to the policy + known secret registry.
    ///
    /// Order:
    ///   1. Known literal secrets (defence in depth — never miss a value).
    ///   2. Regex rules (with optional capture-group narrowing).
    ///   3. Length cap for hostile / oversized strings (REDACT-TC-009).
    pub fn redact_text(&self, input: &str) -> String {
        // 1. Literal known secrets.
        let mut text = input.to_string();
        for known in self.known.snapshot() {
            if !known.is_empty() {
                text = text.replace(&known, REDACTION_MARKER);
            }
        }

        // 2. Regex rules.
        for rule in &self.policy.rules {
            text = match rule.redact_group {
                None => rule
                    .pattern
                    .replace_all(&text, REDACTION_MARKER)
                    .into_owned(),
                Some(group) => rule
                    .pattern
                    .replace_all(&text, |caps: &regex::Captures<'_>| {
                        let full = caps.get(0).map(|m| m.as_str()).unwrap_or("");
                        match caps.get(group) {
                            Some(target) => full.replace(target.as_str(), REDACTION_MARKER),
                            None => full.to_string(),
                        }
                    })
                    .into_owned(),
            };
        }

        // 3. Length cap.
        if text.len() > MAX_PROTOCOL_STRING_LEN {
            // Find the last char boundary inside the budget so we
            // never split a UTF-8 codepoint.
            let mut cut = MAX_PROTOCOL_STRING_LEN;
            while cut > 0 && !text.is_char_boundary(cut) {
                cut -= 1;
            }
            let mut truncated = text[..cut].to_string();
            truncated.push_str("…[truncated]");
            text = truncated;
        }

        text
    }

    /// True if the policy classifies `field_name` as a sensitive
    /// structured field that must be redacted wholesale.
    pub fn is_sensitive_field(&self, field_name: &str) -> bool {
        let lower = field_name.to_lowercase();
        self.policy
            .redact_field_substrings
            .iter()
            .any(|needle| lower.contains(needle))
    }
}
