use crate::{base64, error::TorError};
use base32::{self, Alphabet};
use curve25519_dalek::Scalar;
use ed25519_dalek::{
    hazmat::{raw_sign, ExpandedSecretKey},
    Signature, SignatureError, Signer, SigningKey as DalekSigningKey, Verifier, VerifyingKey,
};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};
use sha2::Sha512;
use sha3::{Digest, Sha3_256};
const TOR_VERSION: u8 = 3;
#[derive(Clone, Deserialize, Serialize, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TorServiceId(String);
impl From<TorServiceId> for String {
    fn from(id: TorServiceId) -> String {
        id.0
    }
}
impl std::fmt::Display for TorServiceId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}
impl std::convert::From<VerifyingKey> for TorServiceId {
    fn from(verifying_key: VerifyingKey) -> Self {
        let version = &[TOR_VERSION];
        let verifying_key_bytes = verifying_key.as_bytes();
        let checksum = TorServiceId::calculate_checksum(verifying_key_bytes.as_ref());
        let mut onion_bytes = verifying_key_bytes.to_vec();
        onion_bytes.extend_from_slice(&checksum);
        onion_bytes.extend_from_slice(version);
        Self(base32::encode(Alphabet::RFC4648 { padding: false }, &onion_bytes).to_lowercase())
    }
}
impl std::str::FromStr for TorServiceId {
    type Err = TorError;
    fn from_str(service_id: &str) -> Result<Self, Self::Err> {
        let onion_bytes = match base32::decode(Alphabet::RFC4648 { padding: false }, service_id) {
            Some(bytes) => bytes,
            None => return Err(TorError::protocol_error("Error base32 decoding service ID")),
        };
        let mut verifying_key_bytes = [0u8; 32];
        verifying_key_bytes.copy_from_slice(&onion_bytes[..32]);
        let mut checksum = [0u8; 2];
        checksum.copy_from_slice(&onion_bytes[32..34]);
        let verifying_checksum = Self::calculate_checksum(&verifying_key_bytes);
        if checksum != verifying_checksum {
            return Err(TorError::protocol_error("Invalid checksum"));
        }
        Ok(Self(service_id.to_string()))
    }
}
impl TorServiceId {
    pub fn generate() -> Self {
        let signing_key = DalekSigningKey::generate(&mut OsRng);
        signing_key.verifying_key().into()
    }
    fn calculate_checksum(verifying_key_bytes: &[u8]) -> [u8; 2] {
        let mut checksum_bytes = ".onion checksum".as_bytes().to_vec();
        checksum_bytes.extend_from_slice(verifying_key_bytes);
        checksum_bytes.extend_from_slice(&[TOR_VERSION]);
        let mut checksum = [0u8; 2];
        checksum
            .copy_from_slice(&Sha3_256::default().chain_update(&checksum_bytes).finalize()[..2]);
        checksum
    }
    pub fn verifying_key(&self) -> Result<VerifyingKey, TorError> {
        let onion_bytes = match base32::decode(Alphabet::RFC4648 { padding: false }, &self.0) {
            Some(bytes) => bytes,
            None => return Err(TorError::protocol_error("Error base32 decoding service ID")),
        };
        let mut verifying_key_bytes = [0u8; 32];
        verifying_key_bytes.copy_from_slice(&onion_bytes[..32]);
        let verifying_key = match VerifyingKey::from_bytes(&verifying_key_bytes) {
            Ok(key) => key,
            Err(_) => {
                return Err(TorError::protocol_error(
                    "Error parsing verifying key from bytes",
                ))
            }
        };
        Ok(verifying_key)
    }
    pub fn as_str(&self) -> &str {
        &self.0
    }
    pub fn onion_hostname(&self) -> String {
        format!("{}.onion", self.0)
    }
}
pub type TorBlob = [u8; 64];
#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct TorEd25519SigningKey(#[serde_as(as = "Base64")] TorBlob);
impl TorEd25519SigningKey {
    fn expanded_secret_key(&self) -> ExpandedSecretKey {
        ExpandedSecretKey::from_bytes(&self.0)
    }
    pub fn verifying_key(&self) -> VerifyingKey {
        VerifyingKey::from(&self.expanded_secret_key())
    }
    pub fn scalar(&self) -> Scalar {
        Scalar::from_bytes_mod_order(self.0[..32].try_into().unwrap())
    }
    pub fn from_blob(blob: &str) -> Self {
        let blob = base64::decode(blob).unwrap();
        Self(blob.try_into().unwrap())
    }
    pub fn from_bytes(bytes: [u8; 64]) -> Self {
        Self(bytes)
    }
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
        self.verifying_key().verify(message, signature)
    }
    pub fn to_blob(&self) -> String {
        base64::encode(&self.0)
    }
    pub fn to_bytes(&self) -> [u8; 64] {
        self.0
    }
}
impl std::str::FromStr for TorEd25519SigningKey {
    type Err = TorError;
    fn from_str(key: &str) -> Result<Self, Self::Err> {
        let onion_bytes = match base64::decode(key) {
            Ok(bytes) => bytes,
            Err(error) => {
                return Err(TorError::protocol_error(&format!(
                    "Error decoding key bytes: {}",
                    error
                )))
            }
        };
        let mut key_bytes = [0u8; 64];
        key_bytes.copy_from_slice(&onion_bytes[..64]);
        Ok(Self::from_bytes(key_bytes))
    }
}
impl std::fmt::Display for TorEd25519SigningKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", base64::encode(&self.0))
    }
}
impl From<TorBlob> for TorEd25519SigningKey {
    fn from(blob: TorBlob) -> Self {
        Self(blob)
    }
}
impl From<&DalekSigningKey> for TorEd25519SigningKey {
    fn from(signing_key: &DalekSigningKey) -> Self {
        let blob = Sha512::default()
            .chain_update(signing_key.to_bytes())
            .finalize();
        Self(blob.into())
    }
}
impl Signer<Signature> for TorEd25519SigningKey {
    fn try_sign(&self, message: &[u8]) -> Result<Signature, SignatureError> {
        Ok(raw_sign::<Sha512>(
            &self.expanded_secret_key(),
            message,
            &self.verifying_key(),
        ))
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use serde_json;
    use std::str::FromStr;
    #[test]
    fn test_ed25519v3_service_id() -> Result<(), anyhow::Error> {
        let base64_blob = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
        let signing_key = TorEd25519SigningKey::from_blob(base64_blob);
        let public_key = signing_key.verifying_key();
        assert_eq!(
            "vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad",
            TorServiceId::from(public_key).as_str(),
        );
        Ok(())
    }
    #[test]
    fn test_tor_ed25519v3_sign_verify() -> Result<(), anyhow::Error> {
        let message = b"This is my very secret message";
        let base64_blob = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
        let signing_key = TorEd25519SigningKey::from_blob(base64_blob);
        let signature = signing_key.sign(message);
        assert!(signing_key.verify(message, &signature).is_ok());
        Ok(())
    }
    #[test]
    fn test_to_from_blob() -> Result<(), anyhow::Error> {
        let blob_in = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
        let signing_key = TorEd25519SigningKey::from_blob(blob_in);
        let blob_out = signing_key.to_blob();
        assert_eq!(blob_in, blob_out);
        Ok(())
    }
    #[test]
    fn test_serialize_signing_key() -> Result<(), anyhow::Error> {
        let blob_in = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
        let signing_key = TorEd25519SigningKey::from_blob(blob_in);
        let json_out = serde_json::to_string(&signing_key)?;
        let expected = "\"0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==\"";
        assert_eq!(expected, json_out);
        let deserialized_signing_key: TorEd25519SigningKey = serde_json::from_str(&json_out)?;
        assert_eq!(signing_key, deserialized_signing_key);
        Ok(())
    }
    #[test]
    fn test_serialize_service_id() -> Result<(), anyhow::Error> {
        let service_id =
            TorServiceId::from_str("vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad")?;
        let expected = "\"vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad\"";
        let json_out = serde_json::to_string(&service_id)?;
        assert_eq!(expected, json_out);
        let deserialized_service_id: TorServiceId = serde_json::from_str(&json_out)?;
        assert_eq!(service_id, deserialized_service_id);
        Ok(())
    }
}