//! Append-only Merkle log for NWEP identity events.
//!
//! This module provides the data structures and operations for the NWEP Merkle log, an
//! append-only sequence of identity events (key bindings, rotations, revocations, and
//! anchor changes). Each entry is serialised and handed to a caller-supplied [`LogStorage`]
//! backend; the C library builds a Merkle tree over all entries so that any single entry
//! can be proven to a verifier in O(log n) steps without revealing the rest of the log.
//!
//! The typical workflow is:
//! 1. Construct a [`MerkleLog`] over a [`LogStorage`] implementation.
//! 2. Call [`MerkleLog::append`] for each new [`MerkleEntry`].
//! 3. Periodically snapshot [`MerkleLog::root`] into a [`crate::checkpoint::Checkpoint`].
//! 4. Hand a [`MerkleProof`] (from [`MerkleLog::prove`]) to a client so it can verify
//!    inclusion against a known checkpoint root with [`MerkleProof::verify`].

#![allow(unsafe_op_in_unsafe_fn)]

use crate::ffi;
use crate::error::{check, check_ssize, Error};
use crate::types::{NodeId, MerkleHash, LOG_ENTRY_MAX_SIZE, MERKLE_PROOF_MAX_DEPTH};

/// `MerkleEntryType` identifies the kind of identity event stored in a [`MerkleEntry`].
///
/// Each variant corresponds to a distinct lifecycle operation on a node's identity.
/// The integer discriminants are fixed by the wire format and must not be changed.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MerkleEntryType {
    /// `KeyBinding` records the initial binding of a public key to a node identity.
    KeyBinding = 1,
    /// `KeyRotation` records a key rotation that replaces the node's active public key.
    KeyRotation = 2,
    /// `Revocation` marks a node identity as permanently revoked.
    Revocation = 3,
    /// `AnchorChange` records an addition or removal of an anchor node from the trust set.
    AnchorChange = 4,
}

impl From<ffi::nwep_merkle_entry_type> for MerkleEntryType {
    fn from(t: ffi::nwep_merkle_entry_type) -> Self {
        match t {
            ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_KEY_BINDING => MerkleEntryType::KeyBinding,
            ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_KEY_ROTATION => MerkleEntryType::KeyRotation,
            ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_REVOCATION => MerkleEntryType::Revocation,
            ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_ANCHOR_CHANGE => MerkleEntryType::AnchorChange,
            _ => MerkleEntryType::KeyBinding,
        }
    }
}

impl MerkleEntryType {
    fn to_ffi(&self) -> ffi::nwep_merkle_entry_type {
        match self {
            MerkleEntryType::KeyBinding => ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_KEY_BINDING,
            MerkleEntryType::KeyRotation => ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_KEY_ROTATION,
            MerkleEntryType::Revocation => ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_REVOCATION,
            MerkleEntryType::AnchorChange => ffi::nwep_merkle_entry_type_NWEP_LOG_ENTRY_ANCHOR_CHANGE,
        }
    }
}

/// `MerkleEntry` is a single identity event record stored in the Merkle log.
///
/// Every event that changes the state of a node's identity is recorded as a
/// `MerkleEntry`. The entry carries the new public key, the previous public key
/// (for rotation and revocation events), a recovery key, and a signature by the
/// node's current key that authorises the transition. Entries are serialised and
/// stored by the [`LogStorage`] backend; the C library computes a domain-separated
/// leaf hash over each entry for the Merkle tree (see [`MerkleEntry::leaf_hash`]).
#[derive(Clone, Debug)]
pub struct MerkleEntry {
    /// The kind of identity event this entry represents.
    pub entry_type: MerkleEntryType,
    /// Unix timestamp (seconds) at which the event was created.
    pub timestamp: crate::Tstamp,
    /// The node identity to which this event applies.
    pub node_id: NodeId,
    /// The node's new (or current) Ed25519 public key, 32 bytes.
    pub pubkey: [u8; 32],
    /// The node's previous Ed25519 public key; all-zeros for `KeyBinding` entries.
    pub prev_pubkey: [u8; 32],
    /// An optional Ed25519 recovery key; all-zeros if not set.
    pub recovery_pubkey: [u8; 32],
    /// Ed25519 signature by the authorising key over the entry's canonical message, 64 bytes.
    pub signature: [u8; 64],
}

impl MerkleEntry {
    pub(crate) fn to_ffi(&self) -> ffi::nwep_merkle_entry {
        ffi::nwep_merkle_entry {
            type_: self.entry_type.to_ffi(),
            timestamp: self.timestamp,
            nodeid: ffi::nwep_nodeid { data: self.node_id.0 },
            pubkey: self.pubkey,
            prev_pubkey: self.prev_pubkey,
            recovery_pubkey: self.recovery_pubkey,
            signature: self.signature,
        }
    }

    pub(crate) fn from_ffi(e: &ffi::nwep_merkle_entry) -> Self {
        MerkleEntry {
            entry_type: MerkleEntryType::from(e.type_),
            timestamp: e.timestamp,
            node_id: NodeId(e.nodeid.data),
            pubkey: e.pubkey,
            prev_pubkey: e.prev_pubkey,
            recovery_pubkey: e.recovery_pubkey,
            signature: e.signature,
        }
    }

    /// `encode` serialises the entry into its canonical binary wire format.
    ///
    /// The returned bytes are the exact representation stored by the [`LogStorage`]
    /// backend and passed over the network. Use [`MerkleEntry::decode`] to reverse
    /// the operation.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library serialisation call fails.
    pub fn encode(&self) -> Result<Vec<u8>, Error> {
        let mut buf = vec![0u8; LOG_ENTRY_MAX_SIZE];
        let ffi_entry = self.to_ffi();
        let n = check_ssize(unsafe { ffi::nwep_merkle_entry_encode(buf.as_mut_ptr(), buf.len(), &ffi_entry) })?;
        buf.truncate(n);
        Ok(buf)
    }

    /// `decode` deserialises a `MerkleEntry` from its canonical binary wire format.
    ///
    /// The input is typically a byte slice retrieved from a [`LogStorage`] backend or
    /// received over the network. The bytes must have been produced by [`MerkleEntry::encode`].
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if `data` is malformed or too short.
    pub fn decode(data: &[u8]) -> Result<Self, Error> {
        let mut entry = unsafe { std::mem::zeroed::<ffi::nwep_merkle_entry>() };
        check(unsafe { ffi::nwep_merkle_entry_decode(&mut entry, data.as_ptr(), data.len()) })?;
        Ok(MerkleEntry::from_ffi(&entry))
    }

    /// `leaf_hash` computes the domain-separated Merkle leaf hash for this entry.
    ///
    /// The leaf hash is the value placed in the Merkle tree for this entry. It is
    /// computed by the C library as a domain-separated hash over the entry's fields —
    /// not a plain hash of the serialised bytes — to prevent second-preimage attacks.
    /// This is the same value recorded in a [`MerkleProof`]'s `leaf_hash` field.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the underlying cryptographic operation fails.
    pub fn leaf_hash(&self) -> Result<MerkleHash, Error> {
        let mut hash = ffi::nwep_merkle_hash { data: [0u8; 32] };
        let ffi_entry = self.to_ffi();
        check(unsafe { ffi::nwep_merkle_leaf_hash(&mut hash, &ffi_entry) })?;
        Ok(MerkleHash(hash.data))
    }
}

/// `merkle_node_hash` computes an interior Merkle tree node hash from two child hashes.
///
/// Given the hashes of a left child and a right child in the Merkle tree, this function
/// returns the parent node hash. It is the building block for manually constructing or
/// verifying Merkle trees without a full [`MerkleLog`] instance. Callers typically use
/// this when re-implementing proof verification or constructing partial trees.
///
/// # Errors
///
/// Returns an [`Error`] if the underlying cryptographic operation fails.
///
/// # Examples
///
/// ```no_run
/// use nwep::merkle::{merkle_node_hash, MerkleEntry, MerkleEntryType};
/// use nwep::types::{MerkleHash, NodeId};
///
/// let left: MerkleHash = MerkleHash([0u8; 32]);
/// let right: MerkleHash = MerkleHash([1u8; 32]);
/// let parent = merkle_node_hash(&left, &right).unwrap();
/// ```
pub fn merkle_node_hash(left: &MerkleHash, right: &MerkleHash) -> Result<MerkleHash, Error> {
    let mut hash = ffi::nwep_merkle_hash { data: [0u8; 32] };
    let l = ffi::nwep_merkle_hash { data: left.0 };
    let r = ffi::nwep_merkle_hash { data: right.0 };
    check(unsafe { ffi::nwep_merkle_node_hash(&mut hash, &l, &r) })?;
    Ok(MerkleHash(hash.data))
}

/// `MerkleProof` proves that a specific entry is included in a Merkle log.
///
/// A `MerkleProof` carries the co-path (sibling hashes) from the target leaf up to
/// the Merkle root, along with enough context to re-compute the root and confirm
/// inclusion. Proofs are generated by [`MerkleLog::prove`] and verified by
/// [`MerkleProof::verify`] against a trusted root (typically obtained from a
/// [`crate::checkpoint::Checkpoint`]).
///
/// The proof is compact: for a log with n entries the co-path has at most ⌈log₂(n)⌉
/// hashes, each 32 bytes.
#[derive(Clone, Debug)]
pub struct MerkleProof {
    /// Zero-based position of the proven entry within the log.
    pub index: u64,
    /// Total number of entries in the log at the time the proof was generated.
    pub log_size: u64,
    /// Domain-separated leaf hash of the entry being proven (see [`MerkleEntry::leaf_hash`]).
    pub leaf_hash: MerkleHash,
    /// Co-path sibling hashes from leaf to root, ordered from the leaf's sibling upward.
    pub siblings: Vec<MerkleHash>,
}

impl MerkleProof {
    pub(crate) fn to_ffi(&self) -> ffi::nwep_merkle_proof {
        let mut proof = unsafe { std::mem::zeroed::<ffi::nwep_merkle_proof>() };
        proof.index = self.index;
        proof.log_size = self.log_size;
        proof.leaf_hash = ffi::nwep_merkle_hash { data: self.leaf_hash.0 };
        proof.depth = self.siblings.len().min(MERKLE_PROOF_MAX_DEPTH);
        for (i, s) in self.siblings.iter().take(MERKLE_PROOF_MAX_DEPTH).enumerate() {
            proof.siblings[i] = ffi::nwep_merkle_hash { data: s.0 };
        }
        proof
    }

    pub(crate) fn from_ffi(p: &ffi::nwep_merkle_proof) -> Self {
        let siblings = (0..p.depth).map(|i| MerkleHash(p.siblings[i].data)).collect();
        MerkleProof {
            index: p.index,
            log_size: p.log_size,
            leaf_hash: MerkleHash(p.leaf_hash.data),
            siblings,
        }
    }

    /// `verify` checks this proof against a trusted Merkle root hash.
    ///
    /// The method recomputes the Merkle root by hashing the [`MerkleProof::leaf_hash`]
    /// with each sibling along the co-path and confirms the result equals `root`. It
    /// also validates that `index` is within `log_size`. Call this after retrieving
    /// a `root` from a verified [`crate::checkpoint::Checkpoint`] to confirm that an
    /// entry is genuinely part of the log at that checkpoint.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the computed root does not match `root`, if `index`
    /// is out of range, or if the proof structure is invalid.
    pub fn verify(&self, root: &MerkleHash) -> Result<(), Error> {
        let ffi_proof = self.to_ffi();
        let ffi_root = ffi::nwep_merkle_hash { data: root.0 };
        check(unsafe { ffi::nwep_merkle_proof_verify(&ffi_proof, &ffi_root) })
    }

    /// `encode` serialises the proof into its canonical binary wire format.
    ///
    /// The returned bytes can be sent to a remote verifier who will call
    /// [`MerkleProof::decode`] and then [`MerkleProof::verify`] against a known root.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library serialisation call fails.
    pub fn encode(&self) -> Result<Vec<u8>, Error> {
        let ffi_proof = self.to_ffi();
        let max_size = 8 + 8 + 32 + 4 + MERKLE_PROOF_MAX_DEPTH * 32;
        let mut buf = vec![0u8; max_size];
        let n = check_ssize(unsafe { ffi::nwep_merkle_proof_encode(buf.as_mut_ptr(), buf.len(), &ffi_proof) })?;
        buf.truncate(n);
        Ok(buf)
    }

    /// `decode` deserialises a `MerkleProof` from its canonical binary wire format.
    ///
    /// The input must have been produced by [`MerkleProof::encode`].
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if `data` is malformed or too short.
    pub fn decode(data: &[u8]) -> Result<Self, Error> {
        let mut proof = unsafe { std::mem::zeroed::<ffi::nwep_merkle_proof>() };
        check(unsafe { ffi::nwep_merkle_proof_decode(&mut proof, data.as_ptr(), data.len()) })?;
        Ok(MerkleProof::from_ffi(&proof))
    }
}

/// `LogStorage` is the user-supplied persistence backend for a [`MerkleLog`].
///
/// Implement this trait to provide whatever storage technology fits the application
/// (an in-memory `Vec`, an on-disk file, LevelDB, SQLite, etc.). The [`MerkleLog`]
/// calls these methods whenever it needs to persist or retrieve raw encoded entries;
/// the trait implementation owns the durability guarantee.
///
/// Implementations must be `Send` because a [`MerkleLog`] may be moved to another
/// thread after construction.
pub trait LogStorage: Send + 'static {
    /// `append` durably stores the encoded bytes of the entry at position `index`.
    ///
    /// The C library calls this in strictly increasing `index` order starting from 0.
    /// Implementations should return an error if the write cannot be committed.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] on I/O or storage failure.
    fn append(&mut self, index: u64, entry: &[u8]) -> Result<(), Error>;

    /// `get` retrieves the encoded bytes of the entry at position `index` into `buf`.
    ///
    /// Returns the number of bytes written into `buf`. The caller guarantees that
    /// `buf` is at least `LOG_ENTRY_MAX_SIZE` bytes. If `index` is out of range,
    /// implementations should return an appropriate error code.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if `index` is out of range or on I/O failure.
    fn get(&self, index: u64, buf: &mut [u8]) -> Result<usize, Error>;

    /// `size` returns the total number of entries currently in the log.
    fn size(&self) -> u64;
}

struct StorageCallbacks {
    storage: Box<dyn LogStorage>,
}

unsafe extern "C" fn log_append_cb(user_data: *mut std::ffi::c_void, index: u64, entry: *const u8, entry_len: usize) -> std::ffi::c_int {
    let cb = &mut *(user_data as *mut StorageCallbacks);
    let data = std::slice::from_raw_parts(entry, entry_len);
    match cb.storage.append(index, data) {
        Ok(()) => 0,
        Err(e) => e.code,
    }
}

unsafe extern "C" fn log_get_cb(user_data: *mut std::ffi::c_void, index: u64, buf: *mut u8, buflen: usize) -> ffi::nwep_ssize {
    let cb = &mut *(user_data as *mut StorageCallbacks);
    let slice = std::slice::from_raw_parts_mut(buf, buflen);
    match cb.storage.get(index, slice) {
        Ok(n) => n as ffi::nwep_ssize,
        Err(e) => e.code as ffi::nwep_ssize,
    }
}

unsafe extern "C" fn log_size_cb(user_data: *mut std::ffi::c_void) -> u64 {
    let cb = &*(user_data as *mut StorageCallbacks);
    cb.storage.size()
}

/// `MerkleLog` is the append-only Merkle log over NWEP identity events.
///
/// `MerkleLog` wraps the C library's `nwep_merkle_log` and bridges it to a
/// caller-supplied [`LogStorage`] backend. Entries are appended in order and the
/// C library maintains an incremental Merkle tree so that [`MerkleLog::root`] and
/// [`MerkleLog::prove`] are efficient at any point in time.
///
/// # Thread safety
///
/// `MerkleLog` is `Send` but not `Sync`. Concurrent access from multiple threads
/// requires external synchronisation (e.g. a `Mutex`).
pub struct MerkleLog {
    ptr: *mut ffi::nwep_merkle_log,
    _storage: Box<StorageCallbacks>,
}

unsafe impl Send for MerkleLog {}

impl MerkleLog {
    /// `new` creates a `MerkleLog` backed by the given [`LogStorage`] implementation.
    ///
    /// The `storage` value is moved into the log and kept alive for its lifetime. If the
    /// storage already contains entries from a previous run, the log resumes from where
    /// it left off (the entry count is queried via [`LogStorage::size`]).
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library fails to initialise the log structure.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use nwep::merkle::{MerkleLog, LogStorage};
    /// use nwep::error::Error;
    ///
    /// struct MemStorage(Vec<Vec<u8>>);
    /// impl LogStorage for MemStorage {
    ///     fn append(&mut self, _i: u64, entry: &[u8]) -> Result<(), Error> {
    ///         self.0.push(entry.to_vec()); Ok(())
    ///     }
    ///     fn get(&self, i: u64, buf: &mut [u8]) -> Result<usize, Error> {
    ///         let e = &self.0[i as usize];
    ///         buf[..e.len()].copy_from_slice(e);
    ///         Ok(e.len())
    ///     }
    ///     fn size(&self) -> u64 { self.0.len() as u64 }
    /// }
    ///
    /// let log = MerkleLog::new(MemStorage(vec![])).unwrap();
    /// ```
    pub fn new(storage: impl LogStorage) -> Result<Self, Error> {
        let mut cb = Box::new(StorageCallbacks { storage: Box::new(storage) });
        let ffi_storage = ffi::nwep_log_storage {
            append: Some(log_append_cb),
            get: Some(log_get_cb),
            size: Some(log_size_cb),
            user_data: cb.as_mut() as *mut _ as *mut std::ffi::c_void,
        };
        let mut ptr: *mut ffi::nwep_merkle_log = std::ptr::null_mut();
        check(unsafe { ffi::nwep_merkle_log_new(&mut ptr, &ffi_storage) })?;
        Ok(MerkleLog { ptr, _storage: cb })
    }

    /// `append` serialises `entry` and appends it to the log, returning its zero-based index.
    ///
    /// The C library serialises the entry, passes the bytes to [`LogStorage::append`], and
    /// updates the incremental Merkle tree. After this call, [`MerkleLog::root`] reflects
    /// the new tree root that includes this entry.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if serialisation or the storage callback fails.
    pub fn append(&mut self, entry: &MerkleEntry) -> Result<u64, Error> {
        let ffi_entry = entry.to_ffi();
        let mut index = 0u64;
        check(unsafe { ffi::nwep_merkle_log_append(self.ptr, &ffi_entry, &mut index) })?;
        Ok(index)
    }

    /// `get` retrieves and deserialises the entry at zero-based position `index`.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if `index` is out of range, the storage callback fails,
    /// or deserialisation of the stored bytes fails.
    pub fn get(&mut self, index: u64) -> Result<MerkleEntry, Error> {
        let mut entry = unsafe { std::mem::zeroed::<ffi::nwep_merkle_entry>() };
        check(unsafe { ffi::nwep_merkle_log_get(self.ptr, index, &mut entry) })?;
        Ok(MerkleEntry::from_ffi(&entry))
    }

    /// `size` returns the current number of entries in the log.
    pub fn size(&self) -> u64 {
        unsafe { ffi::nwep_merkle_log_size(self.ptr) }
    }

    /// `root` computes and returns the current Merkle root hash over all log entries.
    ///
    /// The root changes every time a new entry is appended. It is typically snapshotted
    /// into a [`crate::checkpoint::Checkpoint`] for distribution to verifiers.
    /// Returns an error if the log is empty.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the log is empty or if the C library computation fails.
    pub fn root(&mut self) -> Result<MerkleHash, Error> {
        let mut hash = ffi::nwep_merkle_hash { data: [0u8; 32] };
        check(unsafe { ffi::nwep_merkle_log_root(self.ptr, &mut hash) })?;
        Ok(MerkleHash(hash.data))
    }

    /// `prove` generates a [`MerkleProof`] of inclusion for the entry at `index`.
    ///
    /// The returned proof contains the co-path sibling hashes needed to recompute the
    /// current Merkle root from the entry's leaf hash. Callers typically pair the proof
    /// with the entry (retrieved via [`MerkleLog::get`]) and send both to a verifier,
    /// who calls [`MerkleEntry::leaf_hash`] and [`MerkleProof::verify`].
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if `index` is out of range or the C library fails.
    pub fn prove(&mut self, index: u64) -> Result<MerkleProof, Error> {
        let mut proof = unsafe { std::mem::zeroed::<ffi::nwep_merkle_proof>() };
        check(unsafe { ffi::nwep_merkle_log_prove(self.ptr, index, &mut proof) })?;
        Ok(MerkleProof::from_ffi(&proof))
    }

    pub(crate) fn as_ptr(&mut self) -> *mut ffi::nwep_merkle_log {
        self.ptr
    }
}

impl Drop for MerkleLog {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { ffi::nwep_merkle_log_free(self.ptr) }
        }
    }
}
