//! `{{placeholders}}` in URLs and header values. v1: context from request JSON object only.

use anyhow::{anyhow, Result};
use regex::Regex;
use serde_json::Value;
use std::collections::HashMap;

fn normalize_key(inner: &str) -> String {
    inner.trim().trim_matches('$').to_uppercase()
}

fn value_to_string(v: &Value) -> Result<String> {
    match v {
        Value::String(s) => Ok(s.clone()),
        Value::Bool(b) => Ok(if *b { "true".into() } else { "false".into() }),
        Value::Number(n) => Ok(n.to_string()),
        Value::Null => Err(anyhow!("template context value is null")),
        Value::Array(_) | Value::Object(_) => Err(anyhow!("template context value must be scalar")),
    }
}

/// Build lookup map from a JSON object. Keys are normalized to uppercase (same as placeholders).
pub fn context_from_optional(context: &Option<Value>) -> Result<HashMap<String, String>> {
    let mut m: HashMap<String, String> = HashMap::new();
    let Some(Value::Object(map)) = context else {
        return Ok(m);
    };
    for (k, v) in map {
        let nk = normalize_key(k);
        let s = value_to_string(v)?;
        m.insert(nk, s);
    }
    Ok(m)
}

/// Replace `{{ ... }}` segments using normalized uppercase keys. Passes through when no placeholders.
pub fn expand_template(input: &str, ctx: &HashMap<String, String>) -> Result<String> {
    if !input.contains("{{") {
        return Ok(input.to_string());
    }
    let re = Regex::new(r"\{\{\s*([^}]+?)\s*\}\}").expect("template regex");
    let mut out = String::with_capacity(input.len());
    let mut last = 0usize;
    for cap in re.captures_iter(input) {
        let full = cap.get(0).expect("match");
        out.push_str(&input[last..full.start()]);
        let key = normalize_key(&cap[1]);
        let val = ctx
            .get(&key)
            .ok_or_else(|| anyhow!("missing template value for {{{}}}", key))?;
        out.push_str(val);
        last = full.end();
    }
    out.push_str(&input[last..]);
    Ok(out)
}

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

    #[test]
    fn expand_simple() {
        let mut c = HashMap::new();
        c.insert("USERNAME".into(), "foo".into());
        assert_eq!(
            expand_template("https://ex.com/{{username}}", &c).unwrap(),
            "https://ex.com/foo"
        );
    }

    #[test]
    fn dollar_form() {
        let mut c = HashMap::new();
        c.insert("USERS.EMAIL".into(), "a@b.c".into());
        assert_eq!(
            expand_template("x:{{$USERS.EMAIL$}}", &c).unwrap(),
            "x:a@b.c"
        );
    }

    #[test]
    fn context_from_optional_flat() {
        let ctx = Some(json!({"user": "u1"}));
        let m = context_from_optional(&ctx).unwrap();
        assert_eq!(m.get("USER").map(String::as_str), Some("u1"));
    }
}
