//! Building a presentation: the holder forwards a chosen subset of disclosures
//! and proves control of the bound key over exactly this request.
//!
//! The key-binding JWT commits to the verifier (`aud`), the verifier's `nonce`,
//! and a hash of the precise disclosure set, so a captured presentation cannot
//! be replayed to another verifier or padded with extra disclosures.

use crate::base64url;
use crate::disclosure::Disclosure;
use crate::error::Error;
use crate::issue::IssuedSdJwt;
use crate::jws::{self, Header};
use crate::key::SigningKey;
use chrono::{DateTime, Utc};
use sha2::{Digest as _, Sha256};

/// Media type of a key-binding JWT, per the SD-JWT spec.
pub const KEY_BINDING_TYP: &str = "kb+jwt";

/// What the holder commits to when presenting.
#[derive(Debug, Clone)]
pub struct PresentationParams<'a> {
    /// The verifier this presentation is for (`aud` of the key-binding JWT).
    pub audience: &'a str,
    /// The verifier-supplied freshness value (`nonce`).
    pub nonce: &'a str,
    /// When the presentation is made (`iat` of the key-binding JWT).
    pub presented_at: DateTime<Utc>,
    /// Names of the claims to reveal; all other disclosures are withheld.
    pub disclose: &'a [String],
}

/// A holder presentation: `JWS~<selected disclosures>~<key-binding JWT>`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Presentation(String);

impl Presentation {
    /// Borrow the serialized presentation.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Wrap an already-serialized presentation (e.g. one received over the
    /// wire by a verifier).
    #[must_use]
    pub fn from_string(value: String) -> Self {
        Self(value)
    }
}

/// Build a key-bound presentation revealing only the requested claims.
///
/// # Errors
///
/// Returns an [`Error`] if the issued credential is malformed, a disclosure
/// cannot be parsed, or the key-binding signature cannot be produced.
pub fn present(
    issued: &IssuedSdJwt,
    holder_key: &SigningKey,
    params: &PresentationParams<'_>,
) -> Result<Presentation, Error> {
    let mut segments = issued.as_str().split('~').filter(|s| !s.is_empty());
    let jws = segments.next().ok_or_else(|| Error::MalformedSdJwt {
        reason: "issued credential has no JWS segment".to_owned(),
    })?;

    let mut selected = String::new();
    for encoded in segments {
        let disclosure = Disclosure::parse(encoded)?;
        if params.disclose.iter().any(|name| name == disclosure.name()) {
            selected.push_str(encoded);
            selected.push('~');
        }
    }

    // The SD-JWT the key-binding hash covers: `JWS~<selected>~`.
    let mut covered = String::with_capacity(jws.len() + selected.len() + 1);
    covered.push_str(jws);
    covered.push('~');
    covered.push_str(&selected);

    let sd_hash = base64url::encode(Sha256::digest(covered.as_bytes()));
    let kb_payload = serde_json::json!({
        "iat": params.presented_at.timestamp(),
        "aud": params.audience,
        "nonce": params.nonce,
        "sd_hash": sd_hash,
    });
    let kb_header = Header::new(KEY_BINDING_TYP, None);
    let kb_jwt = jws::encode(&kb_header, &kb_payload, holder_key)?;

    Ok(Presentation(format!("{covered}{kb_jwt}")))
}
