//! Merkle log checkpoints signed by a BLS anchor quorum.
//!
//! A [`Checkpoint`] is a signed snapshot of the Merkle log at a particular moment.
//! It records the log size, the Merkle root hash, and a BLS aggregate signature from
//! a quorum of anchor nodes. Clients use checkpoints as trust anchors: given a valid
//! checkpoint and a [`MerkleProof`](crate::merkle::MerkleProof), they can verify any
//! identity in the log without querying the log server directly.
//!
//! The signing flow is:
//! 1. One anchor creates a proposal: `Checkpoint::new(...)`.
//! 2. Each anchor in the quorum calls [`Checkpoint::sign`] to add its BLS signature.
//! 3. Once the quorum threshold is reached, [`Checkpoint::verify`] will succeed.

use crate::ffi;
use crate::error::{check, check_ssize, Error};
use crate::types::{MerkleHash, Tstamp};
use crate::bls::{BlsKeypair, BlsPubkey, BlsSig};
use crate::anchor::AnchorSet;

/// `CHECKPOINT_MESSAGE_SIZE` is the byte length of the canonical message that anchors sign.
///
/// The message is `epoch (8) + timestamp (8) + merkle_root (32) + log_size (8) = 56` bytes.
pub const CHECKPOINT_MESSAGE_SIZE: usize = 56;

/// `Checkpoint` is a BLS-signed snapshot of the Merkle log at a given epoch.
///
/// `Checkpoint` records the Merkle root, log size, and the aggregate BLS signature
/// from anchor nodes that attested to this state. Use [`Checkpoint::verify`] to
/// confirm that a quorum of trusted anchors signed the checkpoint before trusting
/// any identity proofs that reference it.
#[derive(Clone, Debug)]
pub struct Checkpoint {
    /// Monotonically increasing checkpoint sequence number.
    pub epoch: u64,
    /// Unix timestamp (nanoseconds) when the checkpoint was created.
    pub timestamp: Tstamp,
    /// Merkle root hash of the log at this checkpoint.
    pub merkle_root: MerkleHash,
    /// Number of entries in the log at this checkpoint.
    pub log_size: u64,
    /// Aggregated BLS signature from all signing anchor nodes.
    pub signature: BlsSig,
    /// BLS public keys of the anchors that contributed to `signature`.
    pub signers: Vec<BlsPubkey>,
}

impl Checkpoint {
    /// `new` creates an unsigned checkpoint for the given log state.
    ///
    /// After creation, each anchor in the quorum must call [`sign`](Checkpoint::sign)
    /// to add its BLS signature before the checkpoint can be verified.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the C library call fails (e.g. invalid parameters).
    pub fn new(epoch: u64, timestamp: Tstamp, merkle_root: MerkleHash, log_size: u64) -> Result<Self, Error> {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        let ffi_root = ffi::nwep_merkle_hash { data: merkle_root.0 };
        check(unsafe { ffi::nwep_checkpoint_new(&mut cp, epoch, timestamp, &ffi_root, log_size) })?;
        Ok(Checkpoint::from_ffi(&cp))
    }

    /// `sign` adds this anchor's BLS signature to the checkpoint.
    ///
    /// Multiple anchors may call `sign` independently; the resulting signatures are
    /// aggregated by the C library. Once enough anchors have signed, [`verify`](Checkpoint::verify)
    /// will succeed against the corresponding [`AnchorSet`].
    ///
    /// # Errors
    ///
    /// Returns `Err` if signing fails (e.g. invalid keypair or message).
    pub fn sign(&mut self, anchor_kp: &BlsKeypair) -> Result<(), Error> {
        let mut ffi_cp = self.to_ffi();
        check(unsafe { ffi::nwep_checkpoint_sign(&mut ffi_cp, anchor_kp.as_ffi()) })?;
        *self = Checkpoint::from_ffi(&ffi_cp);
        Ok(())
    }

    /// `verify` checks that a quorum of anchors from `anchor_set` have signed this checkpoint.
    ///
    /// Confirms that the aggregate BLS signature in `self.signature` is a valid signature
    /// by at least `anchor_set.threshold()` of the registered anchor keys over the canonical
    /// checkpoint message (epoch + timestamp + merkle_root + log_size).
    ///
    /// # Errors
    ///
    /// Returns `Err` if the signature is invalid, insufficient signers, or a C error occurs.
    pub fn verify(&self, anchor_set: &AnchorSet) -> Result<(), Error> {
        let ffi_cp = self.to_ffi();
        check(unsafe { ffi::nwep_checkpoint_verify(&ffi_cp, anchor_set.as_ptr()) })
    }

    /// `encode` serializes the checkpoint to a byte vector for storage or transmission.
    ///
    /// # Errors
    ///
    /// Returns `Err` if serialization fails.
    pub fn encode(&self) -> Result<Vec<u8>, Error> {
        let ffi_cp = self.to_ffi();
        let mut buf = vec![0u8; 4096];
        let n = check_ssize(unsafe { ffi::nwep_checkpoint_encode(buf.as_mut_ptr(), buf.len(), &ffi_cp) })?;
        buf.truncate(n);
        Ok(buf)
    }

    /// `decode` deserializes a checkpoint from bytes produced by [`encode`](Checkpoint::encode).
    ///
    /// # Errors
    ///
    /// Returns `Err` if `data` is truncated or malformed.
    pub fn decode(data: &[u8]) -> Result<Self, Error> {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        check(unsafe { ffi::nwep_checkpoint_decode(&mut cp, data.as_ptr(), data.len()) })?;
        Ok(Checkpoint::from_ffi(&cp))
    }

    /// `message` returns the canonical [`CHECKPOINT_MESSAGE_SIZE`]-byte byte string that anchors sign.
    ///
    /// The message is the concatenation of epoch, timestamp, merkle_root, and log_size,
    /// all in big-endian byte order. This is the exact byte string passed to BLS signing.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the C library call fails.
    pub fn message(&self) -> Result<Vec<u8>, Error> {
        let ffi_cp = self.to_ffi();
        let mut buf = vec![0u8; 64];
        let n = check_ssize(unsafe { ffi::nwep_checkpoint_message(buf.as_mut_ptr(), buf.len(), &ffi_cp) })?;
        buf.truncate(n);
        Ok(buf)
    }

    pub(crate) fn to_ffi(&self) -> ffi::nwep_checkpoint {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        cp.epoch = self.epoch;
        cp.timestamp = self.timestamp;
        cp.merkle_root = ffi::nwep_merkle_hash { data: self.merkle_root.0 };
        cp.log_size = self.log_size;
        cp.signature = ffi::nwep_bls_sig { data: self.signature.0 };
        cp.num_signers = self.signers.len().min(crate::types::MAX_ANCHORS);
        for (i, s) in self.signers.iter().take(crate::types::MAX_ANCHORS).enumerate() {
            cp.signers[i] = ffi::nwep_bls_pubkey { data: s.0 };
        }
        cp
    }

    pub(crate) fn from_ffi(cp: &ffi::nwep_checkpoint) -> Self {
        let signers = (0..cp.num_signers)
            .map(|i| BlsPubkey(cp.signers[i].data))
            .collect();
        Checkpoint {
            epoch: cp.epoch,
            timestamp: cp.timestamp,
            merkle_root: MerkleHash(cp.merkle_root.data),
            log_size: cp.log_size,
            signature: BlsSig(cp.signature.data),
            signers,
        }
    }
}
