use serde_json::Value;

/// Redaction option:
/// - `true` => redact everything (returns `{}` and hits `["*"]`)
/// - list of dot paths => remove those fields if they exist (records only hits)
#[derive(Debug, Clone)]
pub enum RedactOption {
    All,
    Paths(Vec<String>),
}

impl From<bool> for RedactOption {
    fn from(b: bool) -> Self {
        if b {
            RedactOption::All
        } else {
            RedactOption::Paths(vec![])
        }
    }
}

impl From<Vec<String>> for RedactOption {
    fn from(v: Vec<String>) -> Self {
        RedactOption::Paths(v)
    }
}

/// Returns (redacted_value, hit_paths).
///
/// Semantics:
/// - Path segments are object keys separated by '.'
/// - "user" removes the entire `user` subtree
/// - "user.name" removes only that leaf key
/// - Only object traversal is supported (no array indices); if traversal hits non-object, it's a miss
pub fn redact_value(input: &Value, redact_option: &RedactOption) -> (Value, Vec<String>) {
    match redact_option {
        RedactOption::All => (Value::Object(Default::default()), vec!["*".to_string()]),
        RedactOption::Paths(paths) => {
            if paths.is_empty() {
                return (input.clone(), vec![]);
            }

            let mut out = input.clone();
            let mut hits: Vec<String> = Vec::new();
            let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();

            for path in paths {
                let path = path.trim();
                if path.is_empty() {
                    continue;
                }

                if delete_dot_path(&mut out, path) && seen.insert(path.to_string()) {
                    hits.push(path.to_string());
                }
            }

            (out, hits)
        }
    }
}

fn delete_dot_path(root: &mut Value, path: &str) -> bool {
    let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
    if parts.is_empty() {
        return false;
    }

    // Single-key delete at root object
    if parts.len() == 1 {
        if let Value::Object(map) = root {
            return map.remove(parts[0]).is_some();
        }
        return false;
    }

    // Traverse to parent object
    let mut cur: &mut Value = root;
    for key in &parts[..parts.len() - 1] {
        match cur {
            Value::Object(map) => {
                if !map.contains_key(*key) {
                    return false;
                }
                // Get mutable ref to next value
                cur = map.get_mut(*key).expect("checked contains_key");
            }
            _ => return false,
        }
    }

    // Delete final key from parent object
    let last = parts[parts.len() - 1];
    match cur {
        Value::Object(map) => map.remove(last).is_some(),
        _ => false,
    }
}

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

    #[test]
    fn redact_leaf_and_subtree_hits_only() {
        let v = json!({
            "user": { "name": "Ana", "email": "a@x.com" },
            "session": { "id": "s1" },
            "arr": [ { "id": 1 } ]
        });

        let (out, hits) = redact_value(
            &v,
            &RedactOption::Paths(vec![
                "user.email".to_string(),
                "missing".to_string(),
                "session".to_string(),
                "arr.0.id".to_string(), // unsupported => miss
            ]),
        );

        assert_eq!(hits, vec!["user.email", "session"]);
        assert_eq!(
            out,
            json!({
                "user": { "name": "Ana" },
                "arr": [ { "id": 1 } ]
            })
        );
    }

    #[test]
    fn redact_all() {
        let v = json!({"a": 1});
        let (out, hits) = redact_value(&v, &RedactOption::All);
        assert_eq!(out, json!({}));
        assert_eq!(hits, vec!["*"]);
    }
}
