use std::str::FromStr;

use borsh::BorshDeserialize;
use ed25519_dalek::SigningKey;
use near_client::crypto::prelude::*;
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaChaRng;

#[test]
fn try_from_bytes_ed25519() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);
    let _ = Ed25519PublicKey::try_from_bytes(pk.as_bytes()).unwrap();

    assert!(matches!(
        Ed25519PublicKey::try_from_bytes(&[0, 0, 0]),
        Err(Error::ConvertFromBytes { .. })
    ));

    assert!(matches!(
        Ed25519SecretKey::try_from_bytes(&[0, 0, 0]),
        Err(Error::ConvertFromBytes { .. })
    ));
}

#[test]
fn to_string_ed25519() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);

    assert_eq!(
        format!("ed25519:{}", bs58::encode(sk.as_bytes()).into_string()),
        sk.string()
    );
    assert_eq!(
        format!("ed25519:{}", bs58::encode(pk.as_bytes()).into_string()),
        pk.string()
    );
}

#[test]
fn from_string_ed25519() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);

    let _ = Ed25519SecretKey::from_string(
        format!("ed25519:{}", bs58::encode(sk.as_bytes()).into_string()).as_str(),
    )
    .unwrap();

    let _ = Ed25519PublicKey::from_string(
        format!("ed25519:{}", bs58::encode(pk.as_bytes()).into_string()).as_str(),
    )
    .unwrap();

    assert!(matches!(
        Ed25519PublicKey::from_string(
            format!("x25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str()
        ),
        Err(Error::WrongKeyType { .. })
    ));

    assert!(matches!(
        Ed25519SecretKey::from_string(
            format!("x25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str(),
        ),
        Err(Error::WrongKeyType { .. })
    ));

    assert!(matches!(
        Ed25519SecretKey::from_string(format!("ed25519:{}", "==1234%#").as_str(),),
        Err(Error::ConvertFromString { .. })
    ));

    assert!(matches!(
        Ed25519PublicKey::from_string(
            format!("ed25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str(),
        ),
        Err(Error::ConvertFromBytes { .. })
    ));

    assert!(matches!(
        Ed25519PublicKey::from_string(format!("ed25519:{}", "==1234%#").as_str(),),
        Err(Error::ConvertFromString { .. })
    ));
}

#[test]
fn from_expanded() {
    let sk = SigningKey::from_bytes(&random_bits());
    let exp_str = format!("ed25519:{}", bs58::encode(sk.to_bytes()).into_string());
    let sk = Ed25519SecretKey::from_expanded(&exp_str).unwrap();

    assert_eq!(sk.as_bytes(), &sk.to_bytes());
}

#[test]
fn from_expanded_fail() {
    let sk = SigningKey::from_bytes(&random_bits());
    let exp_str = bs58::encode(sk.to_bytes()).into_string();
    assert!(matches!(
        Ed25519SecretKey::from_expanded(&exp_str),
        Err(Error::UnknownKeyType { .. })
    ));

    let exp_str = format!("ed25519:{}", bs58::encode(vec![0, 0, 0]).into_string());
    assert!(matches!(
        Ed25519SecretKey::from_expanded(&exp_str),
        Err(Error::ConvertFromBytes { .. })
    ));

    let exp_str = format!("ed25519:{}", "===%123@@#31");
    assert!(matches!(
        Ed25519SecretKey::from_expanded(&exp_str),
        Err(Error::ConvertFromString { .. })
    ));
}

#[test]
fn try_from_bytes_x25519() {
    let sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = PublicKey::from(&sk);
    let _ = PublicKey::try_from_bytes(&pk.to_bytes()).unwrap();

    assert!(matches!(
        PublicKey::try_from_bytes(&[0, 0, 0]),
        Err(Error::ConvertFromBytes { .. })
    ));

    assert!(matches!(
        SecretKey::try_from_bytes(&[0, 0, 0]),
        Err(Error::ConvertFromBytes { .. })
    ));
}

#[test]
fn to_string_x25519() {
    let sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = PublicKey::from(&sk);

    assert_eq!(
        format!("x25519:{}", bs58::encode(&sk.to_bytes()).into_string()),
        sk.string()
    );
    assert_eq!(
        format!("x25519:{}", bs58::encode(&pk.to_bytes()).into_string()),
        pk.string()
    );
}

#[test]
fn from_string_x25519() {
    let sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = PublicKey::from(&sk);

    let _ = SecretKey::from_string(
        format!("x25519:{}", bs58::encode(&sk.to_bytes()).into_string()).as_str(),
    )
    .unwrap();

    let _ = PublicKey::from_string(
        format!("x25519:{}", bs58::encode(&pk.to_bytes()).into_string()).as_str(),
    )
    .unwrap();

    assert!(matches!(
        PublicKey::from_string(
            format!("ed25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str()
        ),
        Err(Error::WrongKeyType { .. })
    ));

    assert!(matches!(
        SecretKey::from_string(
            format!("ed25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str(),
        ),
        Err(Error::WrongKeyType { .. })
    ));

    assert!(matches!(
        SecretKey::from_string(
            format!("x25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str(),
        ),
        Err(Error::ConvertFromBytes { .. })
    ));

    assert!(matches!(
        SecretKey::from_string(format!("x25519:{}", "==1234%#").as_str(),),
        Err(Error::ConvertFromString { .. })
    ));

    assert!(matches!(
        PublicKey::from_string(
            format!("x25519:{}", bs58::encode(vec![0, 0, 0]).into_string()).as_str(),
        ),
        Err(Error::ConvertFromBytes { .. })
    ));

    assert!(matches!(
        PublicKey::from_string(format!("x25519:{}", "==1234%#").as_str(),),
        Err(Error::ConvertFromString { .. })
    ));
}

#[test]
fn public_key_verify() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);

    let signature = sk.sign(b"message");
    pk.verify(b"message", &signature).unwrap();
}

#[test]
fn keypair_verify() {
    let keypair = Keypair::new(Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap());
    let signature = keypair.sign(b"message");
    keypair.verify(b"message", &signature).unwrap();
}

#[test]
fn key_exchange() {
    let alice_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let alice_pk = PublicKey::from(&alice_sk);

    let bob_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let bob_pk = PublicKey::from(&bob_sk);

    assert_eq!(alice_sk.exchange(&bob_pk), bob_sk.exchange(&alice_pk));
}

#[test]
fn key_exchange_multiple() {
    let alice_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let alice_pk = PublicKey::from(&alice_sk);

    let bob_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let bob_pk = PublicKey::from(&bob_sk);

    let karl_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let karl_pk = PublicKey::from(&karl_sk);

    assert_ne!(karl_sk.exchange(&alice_pk), bob_sk.exchange(&alice_pk));
    assert_ne!(karl_sk.exchange(&karl_pk), karl_sk.to_bytes());
    assert_eq!(alice_sk.exchange(&karl_pk), karl_sk.exchange(&alice_pk));
    assert_eq!(alice_sk.exchange(&bob_pk), bob_sk.exchange(&alice_pk));
}

#[test]
fn borsh_ed25519() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let sk_bytes = borsh::to_vec(&sk).unwrap();
    let pk = Ed25519PublicKey::from(&sk);
    let pk_bytes = borsh::to_vec(&pk).unwrap();

    assert_eq!(pk_bytes.len(), pk.as_bytes().len() + 1);
    assert_eq!(pk_bytes[0], 0);
    assert_eq!(sk_bytes.len(), sk.as_bytes().len());

    let sk = Ed25519SecretKey::try_from_slice(&sk_bytes).unwrap();
    let pk = Ed25519PublicKey::try_from_slice(&pk_bytes).unwrap();

    let signature_bytes = borsh::to_vec(&sk.sign(b"message")).unwrap();
    pk.verify(
        b"message",
        &Ed25519Signature::try_from_slice(&signature_bytes).unwrap(),
    )
    .unwrap();
}

#[test]
fn borsh_x25519() {
    let sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let sk_bytes = borsh::to_vec(&sk).unwrap();
    let pk = PublicKey::from(&sk);
    let pk_bytes = borsh::to_vec(&pk).unwrap();

    // just try to serialize and deserialize. We doesn't support key-exchange for now
    let _ = SecretKey::try_from_slice(&sk_bytes).unwrap();
    let _ = PublicKey::try_from_slice(&pk_bytes).unwrap();
}

#[test]
fn keypair_from_str() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);

    let mut sk_bytes = sk.to_bytes().to_vec();
    sk_bytes.extend_from_slice(&pk.to_bytes());

    let keypair_bs58 = format!("ed25519:{}", bs58::encode(sk_bytes).into_string());

    let keypair = Keypair::from_str(&keypair_bs58).unwrap();

    assert_eq!(keypair.secret_key().to_bytes(), sk.to_bytes());
    assert_eq!(keypair.public_key().to_bytes(), pk.to_bytes());
}

#[test]
fn keypair_to_string() {
    let sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let pk = Ed25519PublicKey::from(&sk);

    let mut sk_bytes = sk.to_bytes().to_vec();
    sk_bytes.extend_from_slice(&pk.to_bytes());

    let keypair_bs58 = format!("ed25519:{}", bs58::encode(sk_bytes).into_string());
    let keypair = Keypair::new(sk);

    assert_eq!(keypair_bs58, keypair.to_string());
}

#[test]
fn convert_from_edwards_to_montgomery() {
    let alice_sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();
    let bob_sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();

    let alice_pk = Ed25519PublicKey::from(&alice_sk);
    let bob_pk = Ed25519PublicKey::from(&bob_sk);

    let alice_sk_dhx = SecretKey::from(alice_sk);
    let bob_sk_dhx = SecretKey::from(bob_sk);

    let alice_pk_dhx = PublicKey::from(alice_pk);
    let bob_pk_dhx = PublicKey::from(bob_pk);

    assert_eq!(
        alice_sk_dhx.exchange(&bob_pk_dhx),
        bob_sk_dhx.exchange(&alice_pk_dhx)
    );
}

#[test]
fn convert_from_edwards_to_montgomery_partially() {
    let alice_sk = SecretKey::try_from_bytes(&random_bits()).unwrap();
    let bob_sk = Ed25519SecretKey::try_from_bytes(&random_bits()).unwrap();

    let alice_pk = PublicKey::from(&alice_sk);
    let bob_pk = Ed25519PublicKey::from(&bob_sk);

    let bob_sk_dhx = SecretKey::from(bob_sk);
    let bob_pk_dhx = PublicKey::from(bob_pk);

    assert_eq!(
        alice_sk.exchange(&bob_pk_dhx),
        bob_sk_dhx.exchange(&alice_pk)
    );
}

fn random_bits() -> [u8; ED25519_SECRET_KEY_LENGTH] {
    let mut chacha = ChaChaRng::from_entropy();
    let mut secret_bytes = [0_u8; ED25519_SECRET_KEY_LENGTH];
    chacha.fill_bytes(&mut secret_bytes);
    secret_bytes
}