use crate::connections::utils::public_key_to_hostname;
use ed25519_dalek::{ExpandedSecretKey, Keypair, PublicKey};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use std::intrinsics::transmute;
use std::sync::Arc;
use x25519_dalek::PublicKey as X25519PublicKey;
use x25519_dalek::{SharedSecret, StaticSecret};

#[derive(Serialize, Deserialize, Debug)]
/// Identity - An ed25519 keypair, required for established a Tor v3 onion service and used to
/// maintain a consistent cryptographic identity for a peer.
pub struct Identity {
    keypair: Keypair,
}

impl Identity {
    /// Initialize a persistent identity
    pub fn initialize(keypair: Keypair) -> Arc<Identity> {
        Arc::new(Identity { keypair })
    }

    /// Initialize an ephemeral identity - used for both ephemeral diffie hellman key exchanges
    /// in addition to anonymous primitives connections to various onion services.
    pub fn initialize_ephemeral_identity() -> Identity {
        let mut csprng = OsRng::default();
        let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
        Identity { keypair }
    }

    pub fn public_key(&self) -> ed25519_dalek::PublicKey {
        self.keypair.public.clone()
    }

    pub fn hostname(&self) -> String {
        public_key_to_hostname(&self.keypair.public)
    }

    /// Perform a diffie hellman exchange between `self` and the `remote_pub_key`
    /// Implementation: this method converts the secret key of `self` into a `StaticSecret`
    /// and the `remote_pub_key` into a `x25519_dalek::PublicKey` such that they can be used
    /// as part of the exchange.
    /// This is used as part of the ephemeral diffie hellman 3-way exchange.
    pub fn edh(&self, remote_pub_key: PublicKey) -> SharedSecret {
        let esk = ExpandedSecretKey::from(&self.keypair.secret).to_bytes();
        let hsk: *const [u8; 32] = unsafe { transmute(esk.as_ptr()) };
        let private_x25519 = StaticSecret::from(unsafe { **&hsk });
        let public_x25519: X25519PublicKey = Identity::to_x25519(remote_pub_key);
        return private_x25519.diffie_hellman(&public_x25519);
    }

    /// helper function that converts an Edwards PublicKey into a Montgomery PublicKey
    /// i.e. ed25519 to x25519
    fn to_x25519(edwards_pub_key: PublicKey) -> X25519PublicKey {
        let edwards = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(edwards_pub_key.to_bytes().as_slice());
        X25519PublicKey::from(edwards.decompress().unwrap().to_montgomery().to_bytes())
    }
}

#[cfg(test)]
mod tests {
    use crate::primitives::identity::Identity;
    use ed25519_dalek::Signer;

    #[test]
    fn test_identity() {
        let alice = Identity::initialize_ephemeral_identity();
        let bob = Identity::initialize_ephemeral_identity();

        assert_eq!(alice.edh(bob.keypair.public).to_bytes(), bob.edh(alice.keypair.public).to_bytes());
        println!("Alice Shared Secret: {} {:?}", alice.hostname(), alice.edh(bob.keypair.public).to_bytes());
        println!("Bob Shared Secret: {} {:?}", bob.hostname(), bob.edh(alice.keypair.public).to_bytes());
    }

    #[test]
    fn test_serialize() {
        // Create an emphemeral identity
        let alice = Identity::initialize_ephemeral_identity();

        // Serialize
        let json = serde_json::to_string(&alice).unwrap();
        println!("{}", json);

        // Deserialize
        let deserialized: Identity = serde_json::from_str(&json).unwrap();
        println!("deserialized = {:?}", deserialized);

        // Check that we can sign something with the deserialized key and check it with the original
        let sig = deserialized.keypair.sign("message".as_bytes());
        assert_eq!(true, alice.keypair.public.verify_strict("message".as_bytes(), &sig).is_ok());
    }
}
