//! Cryptographic identity types for Meerkat comms.

use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
use rand_core::OsRng;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use thiserror::Error;
use zeroize::Zeroize;

/// Errors that can occur during identity operations.
#[derive(Debug, Error)]
pub enum IdentityError {
    #[error("Invalid peer ID format: {0}")]
    InvalidPeerId(String),
    #[error("Invalid base64 encoding: {0}")]
    InvalidBase64(#[from] base64::DecodeError),
    #[error("Invalid key length: expected {expected}, got {actual}")]
    InvalidKeyLength { expected: usize, actual: usize },
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Invalid signature")]
    InvalidSignature,
}

/// Ed25519 public key (32 bytes).
///
/// Serialized as a CBOR byte string (not array) for interoperability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PubKey(pub [u8; 32]);

// Custom serde implementation for PubKey to serialize as byte string (CBOR major type 2)
// instead of array (CBOR major type 4) for cross-implementation compatibility.
impl Serialize for PubKey {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serde_bytes::serialize(&self.0[..], serializer)
    }
}

impl<'de> Deserialize<'de> for PubKey {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
        if bytes.len() != 32 {
            return Err(serde::de::Error::invalid_length(bytes.len(), &"32 bytes"));
        }
        let mut arr = [0u8; 32];
        arr.copy_from_slice(&bytes);
        Ok(PubKey(arr))
    }
}

/// Ed25519 signature (64 bytes).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Signature(pub [u8; 64]);

// Custom serde implementation for Signature since serde doesn't support [u8; 64] by default
impl Serialize for Signature {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serde_bytes::serialize(&self.0[..], serializer)
    }
}

impl<'de> Deserialize<'de> for Signature {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
        if bytes.len() != 64 {
            return Err(serde::de::Error::invalid_length(bytes.len(), &"64 bytes"));
        }
        let mut arr = [0u8; 64];
        arr.copy_from_slice(&bytes);
        Ok(Signature(arr))
    }
}

impl PubKey {
    /// Create a new PubKey from raw bytes.
    pub fn new(bytes: [u8; 32]) -> Self {
        Self(bytes)
    }

    /// True when this key is the all-zero sentinel and not real key material.
    pub fn is_zero(&self) -> bool {
        self.0 == [0u8; 32]
    }

    /// Get the raw bytes.
    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    /// Derive the canonical [`meerkat_core::comms::PeerId`] for this signing
    /// pubkey (typed).
    ///
    /// The derivation is a UUIDv5 hash of the 32-byte pubkey under
    /// the core-owned peer-id namespace. A given pubkey always maps to the
    /// same peer id, so the router and trust store can index by
    /// `PeerId` without handing around the raw pubkey everywhere.
    ///
    /// Note: this is a one-way derivation — `PeerId` is a content hash,
    /// not a reversible encoding. To round-trip the pubkey bytes through
    /// a string (for CBOR/JSON carriers, bootstrap tokens, etc.) use
    /// [`Self::to_pubkey_string`] / [`Self::from_pubkey_string`].
    pub fn to_peer_id(&self) -> meerkat_core::comms::PeerId {
        meerkat_core::comms::PeerId::from_ed25519_pubkey(&self.0)
    }

    /// Encode this pubkey as `"ed25519:<base64>"`.
    ///
    /// Round-trip companion to [`Self::from_pubkey_string`] — use this
    /// whenever the 32-byte pubkey needs to cross a string-shaped carrier
    /// (CBOR/JSON trust-store payloads, inproc bootstrap tokens, transport
    /// advertisements) and be recoverable on the other side.
    ///
    /// Distinct from [`Self::to_peer_id`]: this preserves the pubkey
    /// bytes, `to_peer_id` produces a one-way UUIDv5 routing key.
    pub fn to_pubkey_string(&self) -> String {
        format!("ed25519:{}", BASE64.encode(self.0))
    }

    /// Parse a `"ed25519:<base64>"` pubkey string back to a [`PubKey`].
    ///
    /// Inverse of [`Self::to_pubkey_string`]. Fails if the prefix is
    /// missing, the base64 is malformed, or the decoded length is not
    /// exactly 32 bytes.
    pub fn from_pubkey_string(s: &str) -> Result<Self, IdentityError> {
        let prefix = "ed25519:";
        if !s.starts_with(prefix) {
            return Err(IdentityError::InvalidPeerId(format!(
                "must start with '{prefix}'"
            )));
        }
        let encoded = &s[prefix.len()..];
        let bytes = BASE64.decode(encoded)?;
        if bytes.len() != 32 {
            return Err(IdentityError::InvalidKeyLength {
                expected: 32,
                actual: bytes.len(),
            });
        }
        let mut arr = [0u8; 32];
        arr.copy_from_slice(&bytes);
        Ok(Self(arr))
    }

    /// Verify a signature over data using this public key.
    pub fn verify(&self, data: &[u8], sig: &Signature) -> bool {
        let Ok(verifying_key) = VerifyingKey::from_bytes(&self.0) else {
            return false;
        };
        let signature = ed25519_dalek::Signature::from_bytes(&sig.0);
        verifying_key.verify(data, &signature).is_ok()
    }
}

impl Signature {
    /// Create a new Signature from raw bytes.
    pub fn new(bytes: [u8; 64]) -> Self {
        Self(bytes)
    }

    /// Get the raw bytes.
    pub fn as_bytes(&self) -> &[u8; 64] {
        &self.0
    }
}

/// Ed25519 keypair for signing messages.
#[derive(Debug, Clone)]
pub struct Keypair {
    signing_key: SigningKey,
}

impl Keypair {
    /// Generate a new random keypair.
    pub fn generate() -> Self {
        let signing_key = SigningKey::generate(&mut OsRng);
        Self { signing_key }
    }

    /// Create a keypair from a secret key.
    pub fn from_secret(mut secret: [u8; 32]) -> Self {
        let signing_key = SigningKey::from_bytes(&secret);
        secret.zeroize();
        Self { signing_key }
    }

    /// Get the public key.
    pub fn public_key(&self) -> PubKey {
        PubKey(self.signing_key.verifying_key().to_bytes())
    }

    /// Sign data and return the signature.
    pub fn sign(&self, data: &[u8]) -> Signature {
        let sig = self.signing_key.sign(data);
        Signature(sig.to_bytes())
    }

    /// Return the raw secret bytes for this keypair.
    pub fn secret_bytes(&self) -> [u8; 32] {
        self.signing_key.to_bytes()
    }

    /// Save the keypair to a directory.
    /// Writes `identity.key` (secret, mode 0600) and `identity.pub` (public).
    #[cfg(not(target_arch = "wasm32"))]
    pub async fn save(&self, dir: &Path) -> Result<(), IdentityError> {
        tokio::fs::create_dir_all(dir).await?;

        let key_path = dir.join("identity.key");
        let mut secret_bytes = self.signing_key.to_bytes();
        tokio::fs::write(&key_path, &secret_bytes).await?;
        secret_bytes.zeroize();

        // Set restrictive permissions on private key (Unix only)
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let perms = std::fs::Permissions::from_mode(0o600);
            tokio::fs::set_permissions(&key_path, perms).await?;
        }

        tokio::fs::write(
            dir.join("identity.pub"),
            self.signing_key.verifying_key().to_bytes(),
        )
        .await?;
        Ok(())
    }

    /// Load a keypair from a directory.
    #[cfg(not(target_arch = "wasm32"))]
    pub async fn load(dir: &Path) -> Result<Self, IdentityError> {
        let mut secret_bytes = tokio::fs::read(dir.join("identity.key")).await?;
        if secret_bytes.len() != 32 {
            return Err(IdentityError::InvalidKeyLength {
                expected: 32,
                actual: secret_bytes.len(),
            });
        }
        let mut secret = [0u8; 32];
        secret.copy_from_slice(&secret_bytes);
        secret_bytes.zeroize();
        Ok(Self::from_secret(secret))
    }

    /// Load existing keypair or generate a new one.
    #[cfg(not(target_arch = "wasm32"))]
    pub async fn load_or_generate(dir: &Path) -> Result<Self, IdentityError> {
        let key_path = dir.join("identity.key");
        if tokio::fs::try_exists(&key_path).await? {
            Self::load(dir).await
        } else {
            let keypair = Self::generate();
            keypair.save(dir).await?;
            Ok(keypair)
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use meerkat_core::comms::PeerId;
    use std::mem::size_of;
    use tempfile::TempDir;

    #[test]
    fn test_pubkey_size() {
        assert_eq!(size_of::<PubKey>(), 32);
    }

    #[test]
    fn test_signature_size() {
        assert_eq!(size_of::<Signature>(), 64);
    }

    #[test]
    fn test_pubkey_cbor_roundtrip() {
        let pubkey = PubKey::new([42u8; 32]);
        let mut buf = Vec::new();
        ciborium::into_writer(&pubkey, &mut buf).unwrap();
        let decoded: PubKey = ciborium::from_reader(&buf[..]).unwrap();
        assert_eq!(pubkey, decoded);
    }

    #[test]
    fn test_signature_cbor_roundtrip() {
        let sig = Signature::new([99u8; 64]);
        let mut buf = Vec::new();
        ciborium::into_writer(&sig, &mut buf).unwrap();
        let decoded: Signature = ciborium::from_reader(&buf[..]).unwrap();
        assert_eq!(sig, decoded);
    }

    // Phase 1: PeerId derivation + pubkey-string round-trip tests

    #[test]
    fn test_pubkey_to_peer_id_is_deterministic_uuidv5() {
        let pubkey = PubKey::new([42u8; 32]);
        let a = pubkey.to_peer_id();
        let b = pubkey.to_peer_id();
        assert_eq!(a, b, "derivation must be deterministic over pubkey bytes");
        // PeerId renders as a hyphenated UUID.
        let rendered = a.as_str();
        assert_eq!(rendered.len(), 36, "UUID string length");
        assert_eq!(rendered.matches('-').count(), 4, "UUID has 4 hyphens");
    }

    #[test]
    fn test_pubkey_to_peer_id_differs_across_pubkeys() {
        let id_a = PubKey::new([1u8; 32]).to_peer_id();
        let id_b = PubKey::new([2u8; 32]).to_peer_id();
        assert_ne!(id_a, id_b);
    }

    #[test]
    fn test_peer_id_parses_back_as_uuid() {
        // A PeerId emitted from to_peer_id must round-trip through PeerId::parse.
        let pubkey = PubKey::new([7u8; 32]);
        let peer_id = pubkey.to_peer_id();
        let reparsed = PeerId::parse(&peer_id.as_str()).unwrap();
        assert_eq!(peer_id, reparsed);
    }

    #[test]
    fn test_pubkey_to_pubkey_string_format() {
        let pubkey = PubKey::new([1u8; 32]);
        let encoded = pubkey.to_pubkey_string();
        assert!(encoded.starts_with("ed25519:"));
        let base64_part = &encoded["ed25519:".len()..];
        assert!(
            base64_part
                .chars()
                .all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
        );
        // Base64 of 32 bytes is 44 chars (with padding).
        assert_eq!(encoded.len(), "ed25519:".len() + 44);
    }

    #[test]
    fn test_pubkey_string_roundtrip() {
        let original = PubKey::new([99u8; 32]);
        let encoded = original.to_pubkey_string();
        let recovered = PubKey::from_pubkey_string(&encoded).unwrap();
        assert_eq!(original, recovered);
    }

    // Phase 1: Keypair tests

    #[test]
    fn test_keypair_struct() {
        let keypair = Keypair::generate();
        let _ = keypair.public_key();
        // Just verify it compiles and doesn't panic
    }

    #[test]
    fn test_keypair_generate() {
        let keypair = Keypair::generate();
        let pubkey = keypair.public_key();
        assert_eq!(pubkey.as_bytes().len(), 32);
    }

    #[test]
    fn test_keypair_public_key() {
        let keypair = Keypair::generate();
        let pk1 = keypair.public_key();
        let pk2 = keypair.public_key();
        assert_eq!(pk1, pk2); // Same keypair should produce same pubkey
    }

    #[test]
    fn test_keypair_sign() {
        let keypair = Keypair::generate();
        let data = b"test message";
        let sig = keypair.sign(data);
        assert_eq!(sig.as_bytes().len(), 64);
    }

    #[test]
    fn test_pubkey_verify() {
        let keypair = Keypair::generate();
        let data = b"test message";
        let sig = keypair.sign(data);
        let pubkey = keypair.public_key();
        assert!(pubkey.verify(data, &sig));
    }

    // Phase 1: Key Persistence tests

    #[tokio::test]
    async fn test_keypair_save() {
        let tmp = TempDir::new().unwrap();
        let keypair = Keypair::generate();
        keypair.save(tmp.path()).await.unwrap();
        assert!(tmp.path().join("identity.key").exists());
        assert!(tmp.path().join("identity.pub").exists());
    }

    #[tokio::test]
    async fn test_keypair_load() {
        let tmp = TempDir::new().unwrap();
        let original = Keypair::generate();
        original.save(tmp.path()).await.unwrap();
        let loaded = Keypair::load(tmp.path()).await.unwrap();
        assert_eq!(original.public_key(), loaded.public_key());
    }

    #[tokio::test]
    async fn test_keypair_load_or_generate_existing() {
        let tmp = TempDir::new().unwrap();
        let original = Keypair::generate();
        original.save(tmp.path()).await.unwrap();
        let loaded = Keypair::load_or_generate(tmp.path()).await.unwrap();
        assert_eq!(original.public_key(), loaded.public_key());
    }

    #[tokio::test]
    async fn test_keypair_load_or_generate_new() {
        let tmp = TempDir::new().unwrap();
        assert!(!tmp.path().join("identity.key").exists());
        let keypair = Keypair::load_or_generate(tmp.path()).await.unwrap();
        assert!(tmp.path().join("identity.key").exists());
        assert_eq!(keypair.public_key().as_bytes().len(), 32);
    }

    // Phase 1: Security tests

    #[test]
    fn test_sign_verify_roundtrip() {
        let keypair = Keypair::generate();
        let data = b"important message";
        let sig = keypair.sign(data);
        assert!(keypair.public_key().verify(data, &sig));
    }

    #[test]
    fn test_tamper_detection() {
        let keypair = Keypair::generate();
        let data = b"original message";
        let sig = keypair.sign(data);
        let tampered = b"tampered message";
        assert!(!keypair.public_key().verify(tampered, &sig));
    }

    #[test]
    fn test_wrong_key_rejection() {
        let keypair1 = Keypair::generate();
        let keypair2 = Keypair::generate();
        let data = b"test data";
        let sig = keypair1.sign(data);
        // Verify with wrong key should fail
        assert!(!keypair2.public_key().verify(data, &sig));
    }

    #[tokio::test]
    async fn test_keypair_persistence_roundtrip() {
        let tmp = TempDir::new().unwrap();
        let original = Keypair::generate();
        original.save(tmp.path()).await.unwrap();
        let loaded = Keypair::load(tmp.path()).await.unwrap();
        // Sign with loaded key, verify with original's pubkey
        let data = b"persistence test";
        let sig = loaded.sign(data);
        assert!(original.public_key().verify(data, &sig));
    }
}
