//! Algorithm checks — M01-M06 (RFC 8725 §3.1, §3.2).
//!
//! Strategy: parse the header segment as raw JSON ourselves rather than
//! routing through `jsonwebtoken::decode_header`. The library's `Algorithm`
//! enum has no `None` variant in 9.3.1, so an `alg: none` token would
//! surface as a generic parse error and bury the M01 distinction. Hand-
//! parsing lets us map each rejected family to its own `AuthError` variant
//! (audit log dimension).

use std::str::FromStr;

use crate::engine::raw::parse_header_json;
use crate::verify_config::Algorithm;
use crate::{AuthError, VerifyConfig};

pub(crate) fn run(token: &str, cfg: &VerifyConfig) -> Result<(), AuthError> {
    let header = parse_header_json(token)?;
    let alg = header
        .get("alg")
        .and_then(|v| v.as_str())
        .ok_or(AuthError::HeaderUnparseable)?;

    // M01: explicit `alg: none` (case-insensitive — RFC says lowercase but
    // attackers will probe variants).
    if alg.eq_ignore_ascii_case("none") {
        return Err(AuthError::AlgNone);
    }

    // M03/M04/M05: family-level rejections fire *before* the generic
    // whitelist check so audit logs carry the family signal even when an
    // operator widens the whitelist by mistake.
    if alg.starts_with("HS") {
        return Err(AuthError::AlgHmacRejected);
    }
    if alg.starts_with("RS") || alg.starts_with("PS") {
        return Err(AuthError::AlgRsaRejected);
    }
    if alg.starts_with("ES") {
        return Err(AuthError::AlgEcdsaRejected);
    }

    // M02 + M06: the SSOT for which algorithms are acceptable is
    // `cfg.algorithms`, never `header.alg`. We parse the header's alg only
    // to compare it against the configured set; an unparseable or
    // non-whitelisted value is rejected. This is what RFC 8725 §3.2 calls
    // "use the algorithm sent by the application, not the one sent in the
    // header".
    let parsed = Algorithm::from_str(alg).map_err(|_| AuthError::AlgNotWhitelisted)?;
    if !cfg.algorithms.contains(&parsed) {
        return Err(AuthError::AlgNotWhitelisted);
    }

    Ok(())
}
