
use crate::ffi;
use crate::error::{check, Error};
use crate::types::{NodeId, Identity};

/// `Keypair` holds an Ed25519 keypair used to authenticate a NWEP node.
///
/// The keypair is stored in a heap-allocated [`Box`] so that its memory address remains
/// stable when the `Keypair` value is moved. The C library keeps a raw pointer to the
/// keypair for TLS signing during handshakes; if the data were allowed to move, that
/// pointer would become dangling.
///
/// Key material is zeroed on drop via [`nwep_keypair_clear`].
///
/// # Examples
///
/// ```no_run
/// use nwep::Keypair;
/// nwep::init().unwrap();
/// let kp = Keypair::generate().unwrap();
/// println!("node id: {}", kp.node_id().unwrap());
/// ```
pub struct Keypair {
    // Heap-allocated so the nwep_keypair address stays stable when Keypair moves.
    // The C library stores a pointer to the keypair for TLS signing, so the data
    // must not move after nwep_server_new / nwep_client_new is called.
    inner: Box<ffi::nwep_keypair>,
}

impl Keypair {
    /// `generate` creates a new Ed25519 keypair from OS-provided randomness.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the underlying C library call fails.
    pub fn generate() -> Result<Self, Error> {
        let mut kp = Box::new(ffi::nwep_keypair { pubkey: [0u8; 32], privkey: [0u8; 64] });
        check(unsafe { ffi::nwep_keypair_generate(kp.as_mut()) })?;
        Ok(Keypair { inner: kp })
    }

    /// `from_seed` derives an Ed25519 keypair from a 32-byte seed.
    ///
    /// The seed is the first 32 bytes of the standard 64-byte Ed25519 private key
    /// representation (`seed || pubkey`). Passing the same seed always produces the
    /// same keypair.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn from_seed(seed: &[u8; 32]) -> Result<Self, Error> {
        let mut kp = Box::new(ffi::nwep_keypair { pubkey: [0u8; 32], privkey: [0u8; 64] });
        check(unsafe { ffi::nwep_keypair_from_seed(kp.as_mut(), seed.as_ptr()) })?;
        Ok(Keypair { inner: kp })
    }

    /// `from_privkey` reconstructs a keypair from a 64-byte Ed25519 private key.
    ///
    /// The 64-byte private key is the concatenation `seed || pubkey`. The public key is
    /// re-derived from the seed by the C library and the provided public-key suffix is
    /// used for validation.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the key material is invalid or the C library call fails.
    pub fn from_privkey(privkey: &[u8; 64]) -> Result<Self, Error> {
        let mut kp = Box::new(ffi::nwep_keypair { pubkey: [0u8; 32], privkey: [0u8; 64] });
        check(unsafe { ffi::nwep_keypair_from_privkey(kp.as_mut(), privkey.as_ptr()) })?;
        Ok(Keypair { inner: kp })
    }

    /// `public_key` returns the 32-byte Ed25519 public key.
    pub fn public_key(&self) -> [u8; 32] {
        self.inner.pubkey
    }

    /// `seed` returns the 32-byte Ed25519 seed (first half of the private key).
    ///
    /// The seed is sufficient to fully reconstruct the keypair via [`Keypair::from_seed`].
    pub fn seed(&self) -> [u8; 32] {
        let mut seed = [0u8; 32];
        seed.copy_from_slice(&self.inner.privkey[..32]);
        seed
    }

    /// `private_key` returns the full 64-byte Ed25519 private key (`seed || pubkey`).
    pub fn private_key(&self) -> [u8; 64] {
        self.inner.privkey
    }

    /// `node_id` derives the [`NodeId`] for this keypair by hashing the public key.
    ///
    /// The NodeId uniquely identifies a node on the network and is embedded in NWEP
    /// addresses, ensuring that connection targets cannot be impersonated.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn node_id(&self) -> Result<NodeId, Error> {
        let mut nid = ffi::nwep_nodeid { data: [0u8; 32] };
        check(unsafe { ffi::nwep_nodeid_from_keypair(&mut nid, self.inner.as_ref()) })?;
        Ok(NodeId(nid.data))
    }

    /// `clear` zeroes the key material held by this keypair.
    ///
    /// This is called automatically on drop. Call it explicitly when you need to
    /// erase key material before the value goes out of scope, for example before
    /// passing ownership elsewhere.
    pub fn clear(&mut self) {
        unsafe { ffi::nwep_keypair_clear(self.inner.as_mut()) }
    }

    pub(crate) fn as_ffi(&self) -> &ffi::nwep_keypair {
        self.inner.as_ref()
    }

    pub(crate) fn as_ffi_mut(&mut self) -> &mut ffi::nwep_keypair {
        self.inner.as_mut()
    }
}

// SAFETY: nwep_keypair is plain key-material bytes with no thread-local state.
// The C library accesses the keypair only through the pointer passed to
// nwep_client_new / nwep_server_new, so it is safe to send across threads.
unsafe impl Send for Keypair {}

impl Drop for Keypair {
    fn drop(&mut self) {
        self.clear();
    }
}

/// `node_id_from_pubkey` derives a [`NodeId`] from a raw 32-byte Ed25519 public key.
///
/// Use this when you have a public key but no full [`Keypair`], for example when
/// verifying the identity of a remote peer.
///
/// # Errors
///
/// Returns an [`Error`] if the C library call fails.
pub fn node_id_from_pubkey(pubkey: &[u8; 32]) -> Result<NodeId, Error> {
    let mut nid = ffi::nwep_nodeid { data: [0u8; 32] };
    check(unsafe { ffi::nwep_nodeid_from_pubkey(&mut nid, pubkey.as_ptr()) })?;
    Ok(NodeId(nid.data))
}

/// `node_id_eq` returns `true` if two [`NodeId`] values are equal.
///
/// Delegates to the C library's constant-time comparison to avoid timing side-channels.
pub fn node_id_eq(a: &NodeId, b: &NodeId) -> bool {
    let fa = ffi::nwep_nodeid { data: a.0 };
    let fb = ffi::nwep_nodeid { data: b.0 };
    unsafe { ffi::nwep_nodeid_eq(&fa, &fb) != 0 }
}

/// `RecoveryAuthority` holds a separate keypair used to sign key revocations.
///
/// Without a recovery authority, a node whose key is compromised cannot be revoked;
/// the old key would remain trusted indefinitely. The recovery authority keypair should
/// be stored offline or in a secure enclave separate from the node's operational key.
///
/// Key material is zeroed on drop.
pub struct RecoveryAuthority {
    inner: ffi::nwep_recovery_authority,
}

impl RecoveryAuthority {
    /// `new` generates a fresh recovery authority with a randomly generated keypair.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn new() -> Result<Self, Error> {
        let mut ra = ffi::nwep_recovery_authority {
            keypair: ffi::nwep_keypair { pubkey: [0u8; 32], privkey: [0u8; 64] },
            initialized: 0,
        };
        check(unsafe { ffi::nwep_recovery_authority_new(&mut ra) })?;
        Ok(RecoveryAuthority { inner: ra })
    }

    /// `from_keypair` constructs a recovery authority from an existing [`Keypair`].
    ///
    /// Use this to restore a recovery authority from a stored keypair, or to designate
    /// a specific key as the recovery authority for a node.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn from_keypair(kp: &Keypair) -> Result<Self, Error> {
        let mut ra = ffi::nwep_recovery_authority {
            keypair: ffi::nwep_keypair { pubkey: [0u8; 32], privkey: [0u8; 64] },
            initialized: 0,
        };
        check(unsafe { ffi::nwep_recovery_authority_from_keypair(&mut ra, kp.as_ffi()) })?;
        Ok(RecoveryAuthority { inner: ra })
    }

    /// `public_key` returns the recovery authority's 32-byte public key, if initialized.
    ///
    /// Returns `None` when the authority has not been fully initialized by the C library.
    pub fn public_key(&self) -> Option<[u8; 32]> {
        let ptr = unsafe { ffi::nwep_recovery_authority_get_pubkey(&self.inner) };
        if ptr.is_null() {
            None
        } else {
            let mut pk = [0u8; 32];
            unsafe { pk.copy_from_slice(std::slice::from_raw_parts(ptr, 32)) };
            Some(pk)
        }
    }

    /// `clear` zeroes all key material held by this recovery authority.
    ///
    /// Called automatically on drop.
    pub fn clear(&mut self) {
        unsafe { ffi::nwep_recovery_authority_clear(&mut self.inner) }
    }

    pub(crate) fn as_ffi(&self) -> &ffi::nwep_recovery_authority {
        &self.inner
    }
}

impl Drop for RecoveryAuthority {
    fn drop(&mut self) {
        self.clear();
    }
}

/// `ManagedIdentity` tracks an NWEP node's full key lifecycle, including rotation and revocation.
///
/// A managed identity maintains the current active keypair along with a history of previous
/// keys that are still within their grace period. During a rotation the old key remains
/// valid for a short time so that in-flight connections are not disrupted. The identity also
/// records whether the node has been revoked via its [`RecoveryAuthority`].
///
/// [`update`](ManagedIdentity::update) must be called periodically so that the C library can
/// advance its internal rotation schedule and expire stale keys.
///
/// Key material is zeroed on drop.
pub struct ManagedIdentity {
    inner: ffi::nwep_managed_identity,
}

impl ManagedIdentity {
    /// `new` initialises a managed identity from an existing keypair and optional recovery authority.
    ///
    /// `now` is the current timestamp used to seed the rotation schedule. Passing `None` for `ra`
    /// creates an identity that cannot be revoked; pass `Some` to enable revocation support.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn new(kp: &Keypair, ra: Option<&RecoveryAuthority>, now: crate::Tstamp) -> Result<Self, Error> {
        let mut mi = unsafe { std::mem::zeroed::<ffi::nwep_managed_identity>() };
        let ra_ptr = ra.map_or(std::ptr::null(), |r| r.as_ffi());
        check(unsafe { ffi::nwep_managed_identity_new(&mut mi, kp.as_ffi(), ra_ptr, now) })?;
        Ok(ManagedIdentity { inner: mi })
    }

    /// `rotate` generates a new keypair and adds it as the active key for this identity.
    ///
    /// The previous key remains valid for a grace period so that peers with cached credentials
    /// can complete in-flight operations. The rotation is signed with the old key to form a
    /// verifiable chain of custody.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the C library call fails.
    pub fn rotate(&mut self, now: crate::Tstamp) -> Result<(), Error> {
        check(unsafe { ffi::nwep_managed_identity_rotate(&mut self.inner, now) })
    }

    /// `update` advances the identity's internal rotation schedule to the given timestamp.
    ///
    /// Call this periodically (e.g. once per second) so that the C library can expire keys
    /// that have passed their grace period and trigger scheduled rotations.
    pub fn update(&mut self, now: crate::Tstamp) {
        unsafe { ffi::nwep_managed_identity_update(&mut self.inner, now) }
    }

    /// `is_revoked` returns `true` if this identity has been revoked.
    ///
    /// A revoked identity should be refused new connections. Revocation is permanent within
    /// the managed identity structure; re-use the underlying node ID is not possible after
    /// revocation.
    pub fn is_revoked(&self) -> bool {
        unsafe { ffi::nwep_managed_identity_is_revoked(&self.inner) != 0 }
    }

    /// `revoke` marks this identity as revoked, signing the revocation with the recovery authority.
    ///
    /// After calling this, [`is_revoked`](ManagedIdentity::is_revoked) returns `true` and the
    /// revocation can be published to peers as a [`Revocation`] struct.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the identity has no recovery authority, or if the C library call
    /// fails.
    pub fn revoke(&mut self, ra: &RecoveryAuthority, now: crate::Tstamp) -> Result<(), Error> {
        check(unsafe { ffi::nwep_managed_identity_revoke(&mut self.inner, ra.as_ffi(), now) })
    }

    /// `node_id` returns the stable [`NodeId`] for this managed identity.
    ///
    /// The node ID is derived from the initial keypair and does not change across rotations.
    pub fn node_id(&self) -> NodeId {
        NodeId(self.inner.nodeid.data)
    }

    /// `active_keypair` returns a copy of the currently active [`Keypair`], if any.
    ///
    /// Returns `None` if the identity has been revoked or has no active key. The returned
    /// keypair is a copy of the C-managed data; it will not be invalidated by subsequent
    /// rotations.
    pub fn active_keypair(&self) -> Option<Keypair> {
        let ptr = unsafe { ffi::nwep_managed_identity_get_active(&self.inner) };
        if ptr.is_null() {
            None
        } else {
            // Copy the keypair data out of the C-managed storage (matching Go's memcpy approach).
            Some(Keypair { inner: unsafe { Box::new(*ptr) } })
        }
    }

    /// `active_keys` returns copies of all keypairs that are currently within their validity window.
    ///
    /// During a rotation grace period both the old and new keypairs are active. Use this when
    /// you need to validate signatures from any currently trusted key, not just the newest one.
    pub fn active_keys(&self) -> Vec<Keypair> {
        let mut ptrs = [std::ptr::null::<ffi::nwep_keypair>(); crate::types::MAX_ACTIVE_KEYS];
        let n = unsafe {
            ffi::nwep_managed_identity_get_active_keys(
                &self.inner,
                ptrs.as_mut_ptr() as *mut *const ffi::nwep_keypair,
                crate::types::MAX_ACTIVE_KEYS,
            )
        };
        (0..n).filter_map(|i| {
            if ptrs[i].is_null() { None } else { Some(Keypair { inner: unsafe { Box::new(*ptrs[i]) } }) }
        }).collect()
    }

    /// `active_pubkey` returns the 32-byte public key of the currently active keypair, if any.
    ///
    /// This is a cheaper alternative to [`active_keypair`](ManagedIdentity::active_keypair) when
    /// only the public key is needed.
    pub fn active_pubkey(&self) -> Option<[u8; 32]> {
        let ptr = unsafe { ffi::nwep_managed_identity_get_active(&self.inner) };
        if ptr.is_null() {
            None
        } else {
            Some(unsafe { (*ptr).pubkey })
        }
    }

    /// `identity` returns an [`Identity`] snapshot containing the current active public key and node ID.
    ///
    /// If there is no active keypair, the public key field in the returned `Identity` is all zeros.
    pub fn identity(&self) -> Identity {
        let pk = self.active_pubkey().unwrap_or([0u8; 32]);
        Identity { pubkey: pk, node_id: self.node_id() }
    }

    /// `clear` zeroes all key material held by this managed identity.
    ///
    /// Called automatically on drop.
    pub fn clear(&mut self) {
        unsafe { ffi::nwep_managed_identity_clear(&mut self.inner) }
    }

    #[allow(dead_code)]
    pub(crate) fn as_ffi(&self) -> &ffi::nwep_managed_identity {
        &self.inner
    }
}

impl Drop for ManagedIdentity {
    fn drop(&mut self) {
        self.clear();
    }
}

/// `verify_revocation` checks the cryptographic signature on a [`Revocation`] record.
///
/// Returns `Ok(())` if the signature is valid, meaning the revocation was produced by the
/// recovery authority that originally registered with the node's managed identity.
///
/// # Errors
///
/// Returns an [`Error`] if the signature is invalid or the C library call fails.
pub fn verify_revocation(rev: &Revocation) -> Result<(), Error> {
    let ffi_rev = rev.to_ffi();
    check(unsafe { ffi::nwep_managed_identity_verify_revocation(&ffi_rev) })
}

/// `Revocation` is the serialised form of a key revocation record for an NWEP node.
///
/// A `Revocation` is produced by [`ManagedIdentity::revoke`] and can be distributed to peers
/// so they stop trusting the revoked node ID. Recipients call [`verify_revocation`] to confirm
/// the record is authentic before acting on it.
#[derive(Clone, Debug)]
pub struct Revocation {
    /// The node ID being revoked.
    pub node_id: NodeId,
    /// The timestamp at which the revocation was issued.
    pub timestamp: crate::Tstamp,
    /// The public key of the recovery authority that signed this revocation.
    pub recovery_pubkey: [u8; 32],
    /// Ed25519 signature over the revocation payload, produced by the recovery authority.
    pub signature: [u8; 64],
}

impl Revocation {
    fn to_ffi(&self) -> ffi::nwep_revocation {
        ffi::nwep_revocation {
            nodeid: ffi::nwep_nodeid { data: self.node_id.0 },
            timestamp: self.timestamp,
            recovery_pubkey: self.recovery_pubkey,
            signature: self.signature,
        }
    }
}
