//! `IdClaim` — the inner JSON payload that lives inside a sealed
//! iddrop Response (request-first) or sealed transfer (offer-first).
//!
//! Spec reference: `draft-vandemeent-iddrop-00` §5.3 + Appendix A.

use crate::nonce::ChallengeNonce;
use serde::{Deserialize, Serialize};

/// Semantic class of the iddrop object. Spec §5 + §9 + matches TBZ
/// `PayloadClass::Identity` family (which is the only L2 class allowed
/// to carry an iddrop response).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SemanticType {
    /// Full identity assertion (= "I am jasper.aint").
    Identity,
    /// Capability grant (= "I grant scope X for time T").
    Capability,
    /// Verifiable credential (= attestation about a subject).
    Credential,
    /// Sub-identity binding (= parent grants child a sub-handle).
    SubIdentity,
}

/// Kind of claim being made. Distinct from `SemanticType` so future
/// iddrop versions can carry, e.g., a Capability that is in fact a
/// Credential about a subject; today they overlap.
pub type ClaimType = SemanticType;

/// Sealed Response object (spec §5.3, Appendix A).
///
/// Required fields per the draft are: `request_id`, `sender_pubkey`,
/// `receiver_pubkey`, `claimed_aint`, `semantic_type`, `provenance_ref`.
/// Extra fields below are RECOMMENDED for full causal binding and are
/// emitted by `tibet-iddrop` by default (= superset of the minimum).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdClaim {
    /// Protocol version, always [`crate::PROTOCOL_VERSION`].
    pub protocol: String,

    /// Request / offer-session id this claim is bound to.
    pub request_id: String,

    /// Receiver-issued challenge nonce. Sender MUST echo unchanged.
    /// Optional in pure offer-first if the offer session carries the
    /// binding; required in request-first (§8.5).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub challenge_nonce: Option<ChallengeNonce>,

    /// Ed25519 pubkey of the sender (hex, 64 chars).
    pub sender_pubkey: String,

    /// Ed25519 pubkey of the receiver (hex, 64 chars).
    pub receiver_pubkey: String,

    /// AINS handle being asserted (e.g. `"jasper.aint"`).
    pub claimed_aint: String,

    /// Semantic class of this claim — see [`SemanticType`].
    pub semantic_type: SemanticType,

    /// Provenance reference — `ains:...`, `jis:...` or equivalent
    /// registry pointer used in Rule 4 (claim provenance).
    pub provenance_ref: String,

    /// Unix-seconds at which the claim was issued.
    pub issued_at: u64,

    /// Unix-seconds after which the claim is no longer valid.
    pub expires_at: u64,

    /// Optional pointer to a previous claim that this one extends.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub causal_parent: Option<String>,

    /// Optional signed proof chain by trusted AINS resolvers or
    /// parent identities.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub proof_chain: Vec<String>,
}

impl IdClaim {
    /// Build a fresh identity claim with default protocol + timestamps.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        request_id: impl Into<String>,
        challenge_nonce: Option<ChallengeNonce>,
        sender_pubkey: impl Into<String>,
        receiver_pubkey: impl Into<String>,
        claimed_aint: impl Into<String>,
        semantic_type: SemanticType,
        provenance_ref: impl Into<String>,
        ttl_seconds: u64,
    ) -> Self {
        let now = current_unix_seconds();
        Self {
            protocol: crate::PROTOCOL_VERSION.to_string(),
            request_id: request_id.into(),
            challenge_nonce,
            sender_pubkey: sender_pubkey.into(),
            receiver_pubkey: receiver_pubkey.into(),
            claimed_aint: claimed_aint.into(),
            semantic_type,
            provenance_ref: provenance_ref.into(),
            issued_at: now,
            expires_at: now + ttl_seconds,
            causal_parent: None,
            proof_chain: Vec::new(),
        }
    }
}

/// Wall-clock seconds since UNIX epoch (UTC).
pub fn current_unix_seconds() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0)
}
