use crate::error::{Error, check};
use crate::ffi;
use crate::types::{BLS_PUBKEY_LEN, BLS_SIG_LEN};
use std::fmt;

/// `BlsPubkey` is a BLS12-381 public key stored as 48 raw bytes.
///
/// The bytes represent the internal (possibly non-serialised) form used by the C library.
/// Call [`bls_pubkey_serialize`] to obtain the canonical wire format, and
/// [`bls_pubkey_deserialize`] to reconstruct a `BlsPubkey` from wire bytes.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlsPubkey(pub [u8; BLS_PUBKEY_LEN]);

impl Default for BlsPubkey {
    fn default() -> Self {
        BlsPubkey([0u8; BLS_PUBKEY_LEN])
    }
}

impl fmt::Debug for BlsPubkey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "BlsPubkey({})", hex::encode_bytes(&self.0))
    }
}

impl From<ffi::nwep_bls_pubkey> for BlsPubkey {
    fn from(p: ffi::nwep_bls_pubkey) -> Self {
        BlsPubkey(p.data)
    }
}

impl From<BlsPubkey> for ffi::nwep_bls_pubkey {
    fn from(p: BlsPubkey) -> Self {
        ffi::nwep_bls_pubkey { data: p.0 }
    }
}

/// `BlsSig` is a BLS12-381 signature stored as 96 raw bytes.
///
/// Individual signatures produced by [`bls_sign`] and aggregate signatures produced by
/// [`bls_aggregate_sigs`] are both represented by this type. Verify individual signatures
/// with [`bls_verify`] and aggregate signatures with [`bls_verify_aggregate`].
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BlsSig(pub [u8; BLS_SIG_LEN]);

impl Default for BlsSig {
    fn default() -> Self {
        BlsSig([0u8; BLS_SIG_LEN])
    }
}

impl fmt::Debug for BlsSig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "BlsSig([{} bytes])", BLS_SIG_LEN)
    }
}

impl From<ffi::nwep_bls_sig> for BlsSig {
    fn from(s: ffi::nwep_bls_sig) -> Self {
        BlsSig(s.data)
    }
}

impl From<BlsSig> for ffi::nwep_bls_sig {
    fn from(s: BlsSig) -> Self {
        ffi::nwep_bls_sig { data: s.0 }
    }
}

/// `BlsKeypair` holds a BLS12-381 keypair for signing messages.
///
/// The private key is a 32-byte scalar; the public key is a 48-byte compressed G1 point.
/// Use [`bls_sign`] to produce a [`BlsSig`] with this keypair.
pub struct BlsKeypair {
    inner: ffi::nwep_bls_keypair,
}

impl BlsKeypair {
    /// `generate` creates a new BLS12-381 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 = ffi::nwep_bls_keypair {
            pubkey: [0u8; BLS_PUBKEY_LEN],
            privkey: [0u8; 32],
        };
        check(unsafe { ffi::nwep_bls_keypair_generate(&mut kp) })?;
        Ok(BlsKeypair { inner: kp })
    }

    /// `from_seed` derives a BLS12-381 keypair from input key material (IKM).
    ///
    /// Uses the BLS key generation algorithm (HKDF-based) specified in the BLS standard.
    /// The IKM must be at least 32 bytes; shorter slices will be rejected by the C library.
    /// The same IKM always produces the same keypair.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the IKM is too short or the C library call fails.
    pub fn from_seed(ikm: &[u8]) -> Result<Self, Error> {
        let mut kp = ffi::nwep_bls_keypair {
            pubkey: [0u8; BLS_PUBKEY_LEN],
            privkey: [0u8; 32],
        };
        check(unsafe { ffi::nwep_bls_keypair_from_seed(&mut kp, ikm.as_ptr(), ikm.len()) })?;
        Ok(BlsKeypair { inner: kp })
    }

    /// `pubkey` returns the 48-byte BLS12-381 public key for this keypair.
    pub fn pubkey(&self) -> BlsPubkey {
        BlsPubkey(self.inner.pubkey)
    }

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

/// `bls_pubkey_serialize` converts a [`BlsPubkey`] from its internal representation to canonical wire bytes.
///
/// The output is the 48-byte compressed G1 point in the format suitable for transmission or
/// storage. Use [`bls_pubkey_deserialize`] to reverse this operation.
///
/// # Errors
///
/// Returns an [`Error`] if the C library call fails (e.g. the key is the point at infinity).
pub fn bls_pubkey_serialize(pk: &BlsPubkey) -> Result<[u8; BLS_PUBKEY_LEN], Error> {
    let ffi_pk = ffi::nwep_bls_pubkey { data: pk.0 };
    let mut out = [0u8; BLS_PUBKEY_LEN];
    check(unsafe { ffi::nwep_bls_pubkey_serialize(out.as_mut_ptr(), &ffi_pk) })?;
    Ok(out)
}

pub fn bls_pubkey_deserialize(data: &[u8; BLS_PUBKEY_LEN]) -> Result<BlsPubkey, Error> {
    let mut pk = ffi::nwep_bls_pubkey {
        data: [0u8; BLS_PUBKEY_LEN],
    };
    check(unsafe { ffi::nwep_bls_pubkey_deserialize(&mut pk, data.as_ptr()) })?;
    Ok(BlsPubkey(pk.data))
}

pub fn bls_sign(kp: &BlsKeypair, msg: &[u8]) -> Result<BlsSig, Error> {
    let mut sig = ffi::nwep_bls_sig {
        data: [0u8; BLS_SIG_LEN],
    };
    check(unsafe { ffi::nwep_bls_sign(&mut sig, kp.as_ffi(), msg.as_ptr(), msg.len()) })?;
    Ok(BlsSig(sig.data))
}

pub fn bls_verify(pk: &BlsPubkey, sig: &BlsSig, msg: &[u8]) -> Result<(), Error> {
    let ffi_pk = ffi::nwep_bls_pubkey { data: pk.0 };
    let ffi_sig = ffi::nwep_bls_sig { data: sig.0 };
    check(unsafe { ffi::nwep_bls_verify(&ffi_pk, &ffi_sig, msg.as_ptr(), msg.len()) })
}

pub fn bls_aggregate_sigs(sigs: &[BlsSig]) -> Result<BlsSig, Error> {
    let ffi_sigs: Vec<ffi::nwep_bls_sig> = sigs
        .iter()
        .map(|s| ffi::nwep_bls_sig { data: s.0 })
        .collect();
    let mut out = ffi::nwep_bls_sig {
        data: [0u8; BLS_SIG_LEN],
    };
    check(unsafe { ffi::nwep_bls_aggregate_sigs(&mut out, ffi_sigs.as_ptr(), ffi_sigs.len()) })?;
    Ok(BlsSig(out.data))
}

pub fn bls_verify_aggregate(pks: &[BlsPubkey], sig: &BlsSig, msg: &[u8]) -> Result<(), Error> {
    let ffi_pks: Vec<ffi::nwep_bls_pubkey> = pks
        .iter()
        .map(|p| ffi::nwep_bls_pubkey { data: p.0 })
        .collect();
    let ffi_sig = ffi::nwep_bls_sig { data: sig.0 };
    check(unsafe {
        ffi::nwep_bls_verify_aggregate(
            ffi_pks.as_ptr(),
            ffi_pks.len(),
            &ffi_sig,
            msg.as_ptr(),
            msg.len(),
        )
    })
}

mod hex {
    pub fn encode_bytes(b: &[u8]) -> String {
        b.iter().map(|x| format!("{:02x}", x)).collect()
    }
}
