//! Identity and public identity (`ARCHITECTURE.md` §3.2).
//!
//! An [`Identity`] is an X25519 encryption key plus an Ed25519 signing key. The
//! serialization, public-key concatenation order, and identity-hash derivation
//! are all byte-exact with the Reticulum reference (verified by the conformance
//! tests at the bottom of this file).

use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
use rand_core::CryptoRngCore;
use sha2::{Digest, Sha256};
use x25519_dalek::{PublicKey, StaticSecret};

use plexus_core::AddressHash;

use crate::error::Error;

/// Length of the truncated identity/destination hash (a protocol constant).
const HASH_LEN: usize = 16;

/// A local identity: the X25519 encryption key plus the Ed25519 signing key we
/// own. Both halves zeroize on drop. Deliberately does **not** implement `Clone`.
pub struct Identity {
    encryption_key: StaticSecret,
    signing_key: SigningKey,
}

/// The public half of an [`Identity`].
///
/// Holds the raw 32-byte X25519 and Ed25519 public keys, in that order — the
/// order they are hashed and announced in.
#[derive(Debug, Clone)]
pub struct PublicIdentity {
    encryption_pub: [u8; 32],
    signing_pub: [u8; 32],
}

impl Identity {
    /// Generate a fresh identity from a CSPRNG.
    pub fn generate<R: CryptoRngCore>(rng: &mut R) -> Self {
        Self {
            encryption_key: StaticSecret::random_from_rng(&mut *rng),
            signing_key: SigningKey::generate(rng),
        }
    }

    /// Load from the 64-byte private serialization: `x25519_priv(32) ||
    /// ed25519_seed(32)`.
    ///
    /// # Errors
    /// Currently infallible, but returns `Result` for API stability as stricter
    /// key validation is added.
    pub fn from_bytes(prv: &[u8; 64]) -> Result<Self, Error> {
        let mut x = [0u8; 32];
        x.copy_from_slice(&prv[..32]);
        let mut e = [0u8; 32];
        e.copy_from_slice(&prv[32..]);
        Ok(Self {
            encryption_key: StaticSecret::from(x),
            signing_key: SigningKey::from_bytes(&e),
        })
    }

    /// Serialize to 64 private bytes: `x25519_priv(32) || ed25519_seed(32)`.
    #[must_use]
    pub fn to_bytes(&self) -> [u8; 64] {
        let mut out = [0u8; 64];
        out[..32].copy_from_slice(&self.encryption_key.to_bytes());
        out[32..].copy_from_slice(&self.signing_key.to_bytes());
        out
    }

    /// The corresponding public identity.
    #[must_use]
    pub fn public(&self) -> PublicIdentity {
        PublicIdentity {
            encryption_pub: PublicKey::from(&self.encryption_key).to_bytes(),
            signing_pub: self.signing_key.verifying_key().to_bytes(),
        }
    }

    /// Identity hash: `truncate16(SHA256(enc_pub || sig_pub))`.
    #[must_use]
    pub fn hash(&self) -> AddressHash {
        self.public().hash()
    }

    /// Sign `data` with the Ed25519 signing key (64-byte signature).
    #[must_use]
    pub fn sign(&self, data: &[u8]) -> [u8; 64] {
        self.signing_key.sign(data).to_bytes()
    }
}

impl PublicIdentity {
    /// Construct from raw public-key halves (X25519 then Ed25519).
    #[must_use]
    pub fn from_parts(encryption_pub: [u8; 32], signing_pub: [u8; 32]) -> Self {
        Self {
            encryption_pub,
            signing_pub,
        }
    }

    /// The raw X25519 encryption public key.
    #[must_use]
    pub fn encryption_pub(&self) -> &[u8; 32] {
        &self.encryption_pub
    }

    /// The raw Ed25519 signing public key.
    #[must_use]
    pub fn signing_pub(&self) -> &[u8; 32] {
        &self.signing_pub
    }

    /// Verify a 64-byte Ed25519 signature over `data`.
    ///
    /// # Errors
    /// [`Error::BadSignature`] if the public key is malformed or the signature
    /// does not verify.
    pub fn verify(&self, data: &[u8], sig: &[u8; 64]) -> Result<(), Error> {
        let vk = VerifyingKey::from_bytes(&self.signing_pub).map_err(|_| Error::BadSignature)?;
        vk.verify_strict(data, &Signature::from_bytes(sig))
            .map_err(|_| Error::BadSignature)
    }

    /// Identity hash: `truncate16(SHA256(enc_pub || sig_pub))`.
    #[must_use]
    pub fn hash(&self) -> AddressHash {
        let mut hasher = Sha256::new();
        hasher.update(self.encryption_pub);
        hasher.update(self.signing_pub);
        let digest = hasher.finalize();
        let mut out = [0u8; HASH_LEN];
        out.copy_from_slice(&digest[..HASH_LEN]);
        AddressHash::new(out)
    }
}

#[cfg(test)]
mod tests {
    use super::Identity;
    use hex_literal::hex;

    // Conformance vectors captured from RNS 1.3.1 with a fixed 0x00..0x3F private
    // key (see crates/plexus-vectors/capture/). Ed25519 is deterministic, so the
    // signature is byte-exact; the X25519 public key tests that dalek's scalar
    // clamping matches the reference.
    const PRV: [u8; 64] =
        hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
    const ENC_PUB: [u8; 32] =
        hex!("8f40c5adb68f25624ae5b214ea767a6ec94d829d3d7b5e1ad1ba6f3e2138285f");
    const SIG_PUB: [u8; 32] =
        hex!("29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7");
    const HASH: [u8; 16] = hex!("aca31af0441d81dbec71e82da0b4b5f5");
    const MSG: &[u8] = b"plexus conformance vector v0";
    const SIG: [u8; 64] =
        hex!("2397f6cb92b6649989bd027576bbaeb1a81e11f11771cd61cbf5f1e03b0b99310688774e656d5e4361a59c07a0e9ac20be72d3b8d7cf202430ae27f39c3b2e07");

    #[test]
    fn public_keys_match_reference() {
        let id = Identity::from_bytes(&PRV).unwrap();
        let pid = id.public();
        assert_eq!(*pid.encryption_pub(), ENC_PUB, "x25519 clamping must match");
        assert_eq!(*pid.signing_pub(), SIG_PUB, "ed25519");
    }

    #[test]
    fn identity_hash_matches_reference() {
        let id = Identity::from_bytes(&PRV).unwrap();
        assert_eq!(*id.hash().as_bytes(), HASH);
    }

    #[test]
    fn signature_matches_reference() {
        let id = Identity::from_bytes(&PRV).unwrap();
        assert_eq!(
            id.sign(MSG),
            SIG,
            "deterministic Ed25519 must be byte-exact"
        );
    }

    #[test]
    fn verify_accepts_good_rejects_tampered() {
        let pid = Identity::from_bytes(&PRV).unwrap().public();
        assert!(pid.verify(MSG, &SIG).is_ok());
        let mut bad = SIG;
        bad[0] ^= 0x01;
        assert!(pid.verify(MSG, &bad).is_err());
    }

    #[test]
    fn private_bytes_round_trip() {
        let id = Identity::from_bytes(&PRV).unwrap();
        assert_eq!(id.to_bytes(), PRV);
    }
}
