//! JWT helpers for credential-bundle building.

use base64::Engine;

/// Decode a JWT's payload and extract the `sub` (subject) claim, falling
/// back to `user_id` if `sub` is absent. Returns a distinct error message
/// per failure mode so callers can surface the real reason in their
/// site-specific error wrapper.
///
/// Distinct error reasons:
/// - `"missing payload segment"` — input doesn't have at least 2 dot-segments
/// - `"payload base64 decode failed"` — the second segment isn't valid
///   url-safe base64 (no padding)
/// - `"payload not utf8"` — base64 decoded but bytes aren't UTF-8
/// - `"payload not JSON"` — UTF-8 decoded but isn't valid JSON
/// - `"missing sub or user_id"` — JSON parsed but neither claim present
pub fn decode_jwt_sub(token: &str) -> Result<String, &'static str> {
    let payload_b64 = token.split('.').nth(1).ok_or("missing payload segment")?;
    let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
        .decode(payload_b64)
        .map_err(|_| "payload base64 decode failed")?;
    let s = std::str::from_utf8(&decoded).map_err(|_| "payload not utf8")?;
    let v: serde_json::Value = serde_json::from_str(s).map_err(|_| "payload not JSON")?;
    v.get("sub")
        .or_else(|| v.get("user_id"))
        .and_then(|x| x.as_str())
        .map(|s| s.to_string())
        .ok_or("missing sub or user_id")
}

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

    #[test]
    fn decode_known_sub() {
        let token = "h.eyJzdWIiOiJhYmMifQ.s";
        assert_eq!(decode_jwt_sub(token).unwrap(), "abc");
    }

    #[test]
    fn decode_falls_back_to_user_id() {
        let token = "h.eyJ1c2VyX2lkIjoieHl6In0.s";
        assert_eq!(decode_jwt_sub(token).unwrap(), "xyz");
    }

    #[test]
    fn missing_segments_returns_err() {
        assert!(matches!(
            decode_jwt_sub("garbage"),
            Err("missing payload segment")
        ));
        assert!(matches!(
            decode_jwt_sub("a.b"),
            Err("payload base64 decode failed")
        ));
    }

    #[test]
    fn malformed_payload_returns_err() {
        // Valid base64 of non-JSON "abc"
        let token = "h.YWJj.s";
        assert!(matches!(decode_jwt_sub(token), Err("payload not JSON")));
    }

    #[test]
    fn missing_sub_returns_err() {
        // Valid JSON without sub or user_id: {"foo":"bar"}
        let token = "h.eyJmb28iOiJiYXIifQ.s";
        assert!(matches!(
            decode_jwt_sub(token),
            Err("missing sub or user_id")
        ));
    }
}
