extern crate cfg_if;
extern crate wasm_bindgen;
extern crate base64;

// use rand::Rng;

mod utils;

// use std::convert::{TryInto};

use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;
use base64::{encode, decode};
// use rand::{thread_rng, Rng};
// use rand::rngs::OsRng;

// use noah_x25519_dalek::x25519;
// use noah_x25519_dalek::StaticSecret;
// use noah_x25519_dalek::PublicKey;
use uuid::Uuid;

// use cosmian_crypto_core::{
//     asymmetric_crypto::{curve25519::{X25519KeyPair, X25519PublicKey, X25519PrivateKey}, DhKeyPair},
//     kdf,
//     reexport::rand_core::SeedableRng,
//     symmetric_crypto::{aes_256_gcm_pure::Aes256GcmCrypto, Dem, SymKey, key::Key},
//     CsRng, KeyTrait,
// };

// use passwords::PasswordGenerator;
use wasm_bindgen::JsValue;
// use serde_wasm_bindgen::to_value;

use aes_gcm::{
    aead::{Aead, KeyInit, OsRng},
    Aes256Gcm, Nonce // Or `Aes128Gcm`
};
use rand::RngCore;

// use js_sys::{ArrayBuffer, Float32Array, DataView};


cfg_if! {
    if #[cfg(feature = "wee_alloc")] {
        extern crate wee_alloc;
        #[global_allocator]
        static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
    }
}

// #[wasm_bindgen]
// extern "C" {
//     fn alert(s: &str);
// }


pub fn new() {
    utils::set_panic_hook();

    // ...
}

#[wasm_bindgen]
pub fn password() -> Result<String, JsValue> {
    let key = Aes256Gcm::generate_key(&mut OsRng);
    let encoded_key = base64::encode(&key);

    Ok(encoded_key)
}
// #[wasm_bindgen]
// pub fn new_chat_id() -> String {
//     let pg = PasswordGenerator {
//         length: 17,
//         numbers: true,
//         lowercase_letters: true,
//         uppercase_letters: true,
//         symbols: true,
//         spaces: false,
//         exclude_similar_characters: false,
//         strict: true,
//     };

//     let password_result = pg.generate_one().unwrap();

//     // Convert the Vec<String> to a JavaScript Array
//     // let array = to_value(&password_result).map_err(JsValue::from)?;

//     password_result
// }
// #[wasm_bindgen]
// pub fn password() -> String {
//     // alert(&format!("Hello,{}!", name));
//     const CHARSET: &[u8] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\
//     abcdefghijklmnopqrstuvwxyz\
//     0123456789){}[](*&^%$#@!~".as_bytes();

// // let mut rng = rand::thread_rng();

// let password_result: String = (0..60)
// .map(|_| {
// let idx = thread_rng().gen_range(0..CHARSET.len());
// CHARSET[idx] as char
// })
// .collect();
// return password_result

// }

#[wasm_bindgen]
pub fn decode_base64(value: String) -> String {
    let decoded = base64::decode_config(&value, base64::STANDARD_NO_PAD).unwrap();
    let stringing = String::from_utf8(decoded).unwrap();

    return stringing
}
#[wasm_bindgen]
pub fn decode_base64_b(value:String) -> Vec<u8>{

    let decoded =  decode(value).unwrap();
    // let stringing = String::from_utf8(decoded).unwrap();

    return  decoded

}
#[wasm_bindgen]
pub fn encode_base64(value:String) -> String{

    let encoded =  encode(value);

    return encoded

}

#[wasm_bindgen]
pub fn uuid_generate() -> String {

    let id = Uuid::new_v4();

    return id.to_string()
}


// #[wasm_bindgen]
// pub fn generate_public_key() -> String {

//     // let rng = rand::thread_rng();
//     // let rng = rand::thread_rng();
//     // let mut rng = OsRng;

//     let ali_secret = StaticSecret::new(&mut OsRng);
//     let sec_bytes = ali_secret.to_bytes();

//     let ali_public = PublicKey::from(&ali_secret);
//     let pub_bytes = ali_public.to_bytes();

//     let sec_enc = encode(sec_bytes);
//     let pub_enc = encode(pub_bytes);
   

//         let vec_pk_sk = vec![sec_enc,pub_enc];
    
//        return vec_pk_sk.iter().cloned().collect::<String>();
        
// }

// #[wasm_bindgen]
// pub fn generate_shared_secret( main_secret:String, peer_pubkey:String ) -> String{

//     let sec_dec:[u8; 32]= (decode(main_secret).unwrap()).try_into().unwrap();
//     let pub_dec:[u8; 32]= (decode(peer_pubkey).unwrap()).try_into().unwrap();
    

//     let create_sec = StaticSecret::from(sec_dec).to_bytes();
//     let create_pub = PublicKey::from(pub_dec).to_bytes();
    

//     let shared_secret = x25519(create_sec, create_pub);
//     // let shared_secret = create_sec.diffie_hellman(&create_pub);

//     // let ss_by = shared_secret.as_bytes();
//     // let ss_enc = hex::encode(ss_by);
//     let ss_enc = hex::encode(shared_secret);
    

//     return ss_enc

// }

// #[wasm_bindgen]
// pub fn generate_pubprv_keys() -> String {

//     let mut rng = CsRng::from_entropy();
//     let keypair = X25519KeyPair::new(&mut rng);
//     let pub_key = keypair.public_key().to_bytes();
//     let prv_key = keypair.private_key().to_bytes();

//     let pub_str = base64::encode( pub_key).replace("=", "");
//     let prv_str = base64::encode( prv_key).replace("=", "");

//     return format!("{},{}", pub_str, prv_str);
// }


#[wasm_bindgen]
pub fn vec_to_base64(v: &[u8]) -> String {
    base64::encode(v)
}

#[wasm_bindgen]
pub fn base64_to_vec(s: &str) -> Vec<u8> {
    base64::decode(s).unwrap()
}

#[wasm_bindgen]
pub fn sanitize_base64(input_string: &str) -> Vec<u8> {
    let sanitized_input = html_escape::decode_html_entities(input_string);
    let decoded = base64::decode(&*sanitized_input).unwrap();
    decoded
}


// #[wasm_bindgen]
// pub fn encrypt_or_decrypt_files(mode: String, public_k:String, private_k:String, file_data: Vec<u8>, additional_data:String) -> Vec<u8> {
//     let pub_decoded = base64::decode(public_k).unwrap();
//     let prv_decoded = base64::decode(private_k).unwrap();
//     let back_pub = X25519PublicKey::try_from_bytes(pub_decoded.as_slice()).expect("Failed to convert public key string to X25519PublicKey");
//     let back_other_prv = X25519PrivateKey::try_from_bytes(prv_decoded.as_slice()).expect("Failed to convert private key string to X25519PrivateKey");
//     let shared_sec = &back_pub * &back_other_prv;
//     let additional_data_bytes = Some(additional_data.as_bytes());

//     const KEY_DERIVATION_INFO: &[u8] = b"Curve25519 KDF derivation";
//     const KEY_LENGTH: usize = Aes256GcmCrypto::KEY_LENGTH;
//     let symmetric_key: Key<{ KEY_LENGTH }> = SymKey::<KEY_LENGTH>::from_bytes(kdf!(
//         KEY_LENGTH,
//         &shared_sec.to_bytes(),
//         KEY_DERIVATION_INFO
//     ));

//     match mode.as_ref() {
//         "encrypt" => {
//             let mut rng = CsRng::from_entropy();
//             // let mut result = Vec::new();
//             // let chunk_size = 10 * 1024 * 1024; // 1 MB chunk size
//             // for chunk in file_data.chunks(chunk_size) {
//                 let c = Aes256GcmCrypto::encrypt(&mut rng, &symmetric_key, &file_data, additional_data_bytes).unwrap();
//                 // result.extend(c);
//             // }
//             // result
//             c
//         }
//         "decrypt" => {
//             // let mut result = Vec::new();
//             // let chunk_size = 10 * 1024 * 1024; // 1 MB chunk size
//             // for chunk in file_data.chunks(chunk_size) {
//                 let dec = Aes256GcmCrypto::decrypt(&symmetric_key, &file_data, additional_data_bytes).unwrap();
//                 // result.extend(dec);
//             // }
//             // result
//             dec
//         }
//         _ => panic!("Invalid mode"),
//     }
    
// }

#[wasm_bindgen]
pub fn encrypt_or_decrypt_files2(
    mode: String,
    key_k: String,
    file_data: Vec<u8>,
) -> Result<Vec<u8>, JsValue> {
    let key_decoded = base64::decode(&key_k).map_err(|e| JsValue::from_str(&e.to_string()))?;
    let cipher = Aes256Gcm::new_from_slice(&key_decoded).unwrap();

    let mut nonce_data = [0u8; 12];
    let mut rng = rand::thread_rng();
    rng.fill_bytes(&mut nonce_data);
    let nonce = Nonce::from_slice(&nonce_data);

    match mode.as_ref() {
        "encrypt" => {
            let ciphertext = cipher
                .encrypt(nonce, file_data.as_ref())
                .map_err(|e| JsValue::from_str(&e.to_string()))?;

            // Concatenate the nonce and ciphertext
            let mut combined = Vec::new();
            combined.extend_from_slice(&nonce_data);
            combined.extend_from_slice(&ciphertext);

            Ok(combined)
        }
        "decrypt" => {
            let (stored_nonce_data, stored_ciphertext) = file_data.split_at(12);
            let stored_nonce = Nonce::from_slice(stored_nonce_data);

            let plaintext = cipher
                .decrypt(stored_nonce, stored_ciphertext)
                .map_err(|e| JsValue::from_str(&e.to_string()))?;

            Ok(plaintext)
        }
        _ => panic!("Invalid mode"),
    }
}

// pub fn encrypt_or_decrypt_files_testing(
//     mode: &str,
//     key_k: &str,
//     file_data: &[u8],
// ) -> Result<Vec<u8>, JsValue> {
//     encrypt_or_decrypt_files2(mode.to_string(), key_k.to_string(), file_data.to_vec())
// }

// #[cfg(test)]
// mod tests {
//     use super::*;

//     #[test]
//     fn test_encrypt_and_decrypt() {
//         let key = Aes256Gcm::generate_key(&mut OsRng);
//         let encoded_key = base64::encode(&key);

//         let plaintext = b"Testing encryption and decryption plus 1";

//         // Test encryption
//         let encrypted_data = encrypt_or_decrypt_files_testing("encrypt", &encoded_key, plaintext).unwrap();

//         // Test decryption
//         let decrypted_data = encrypt_or_decrypt_files_testing("decrypt", &encoded_key, &encrypted_data).unwrap();

//         // Convert decrypted_data to a string and print it
//         let decrypted_string = String::from_utf8(decrypted_data.clone()).unwrap();
//         println!("Decrypted data: {}", decrypted_string);

//         assert_eq!(plaintext.to_vec(), decrypted_data);
//     }
// }