//! Compact ES256 JWS: the signing envelope under both the issuer credential and
//! the holder's key-binding proof.
//!
//! Only ES256 is emitted, and the algorithm is checked on the way in, so a
//! token cannot smuggle in a weaker or `none` algorithm.

use crate::base64url;
use crate::error::Error;
use crate::key::{SigningKey, verify_bytes};
use p256::ecdsa::VerifyingKey;
use serde::{Deserialize, Serialize};

/// The fixed algorithm for every signature this crate produces or accepts.
pub(crate) const ES256: &str = "ES256";

/// A JOSE header, restricted to the fields this crate uses.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct Header {
    /// Signature algorithm; must be `ES256`.
    pub alg: String,
    /// Media type of the token (`dc+sd-jwt`, `kb+jwt`).
    pub typ: String,
    /// Key identifier of the signer, when published.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub kid: Option<String>,
}

impl Header {
    pub(crate) fn new(typ: &str, kid: Option<String>) -> Self {
        Self {
            alg: ES256.to_owned(),
            typ: typ.to_owned(),
            kid,
        }
    }
}

/// Encode and sign a compact JWS over `payload`.
pub(crate) fn encode(
    header: &Header,
    payload: &serde_json::Value,
    key: &SigningKey,
) -> Result<String, Error> {
    let header_json = serde_json::to_vec(header).map_err(Error::json("jws header"))?;
    let payload_json = serde_json::to_vec(payload).map_err(Error::json("jws payload"))?;
    let signing_input = format!(
        "{}.{}",
        base64url::encode(header_json),
        base64url::encode(payload_json)
    );
    let signature = key.sign_bytes(signing_input.as_bytes())?;
    Ok(format!("{signing_input}.{}", base64url::encode(signature)))
}

/// Verify a compact JWS with `key` and return its header and payload.
pub(crate) fn decode_verify(
    token: &str,
    key: &VerifyingKey,
    context: &'static str,
) -> Result<(Header, serde_json::Value), Error> {
    let parts: Vec<&str> = token.split('.').collect();
    let [header_b64, payload_b64, signature_b64] = parts.as_slice() else {
        return Err(Error::MalformedJws { found: parts.len() });
    };
    let signing_input = format!("{header_b64}.{payload_b64}");
    let signature = base64url::decode(signature_b64, "jws signature")?;
    verify_bytes(key, signing_input.as_bytes(), &signature, context)?;

    let header_bytes = base64url::decode(header_b64, "jws header")?;
    let header: Header =
        serde_json::from_slice(&header_bytes).map_err(Error::json("jws header"))?;
    if header.alg != ES256 {
        return Err(Error::BadSignature { context });
    }
    let payload_bytes = base64url::decode(payload_b64, "jws payload")?;
    let payload: serde_json::Value =
        serde_json::from_slice(&payload_bytes).map_err(Error::json("jws payload"))?;
    Ok((header, payload))
}
