use dat::certificate::DatCertificate;
use dat::crypto::DatCryptoAlgorithm::{AES128GCMN, AES256GCMN};
use dat::error::DatError;
use dat::manager::DatManager;
use dat::payload::DatPayload;
use dat::signature::DatSignatureAlgorithm::{P256, P384, P521};
use dat::util::now_unix_timestamp;
use rand::RngExt;
use std::time::Instant;
use tokio::task::JoinSet;

fn rand_string() -> String {
    let mut rng = rand::rng();
    (0..100).map(|_| { rng.sample(rand::distr::Alphanumeric) as char }).collect()
}

async fn loops(multi_thread: bool, loops: i64, certificates: &'static Vec<DatCertificate>, plain: &'static String, secure: &'static String) -> Result<(), DatError> {
    println!("\n{}", if multi_thread { "Multi-Thread" } else { "Single-Thread" });

    for certificate in certificates {
        let pre = format!("{} {}", certificate.signature_algorithm(), certificate.crypto_algorithm());

        let mut dat = String::new();

        let start = Instant::now();
        if multi_thread {
            let mut futures: JoinSet<String> = JoinSet::new();
            for _ in 0..loops {
                futures.spawn(async move {
                    DatManager::_issue(&certificate, &*plain, &*secure).unwrap()
                });
            }
            while let Some(res) = futures.join_next().await {
                dat = res.unwrap();
            }
        } else {
            for _ in 0..loops {
                dat = DatManager::_issue(&certificate, &*plain, &*secure)?;
            }
        }
        let duration = start.elapsed();
        println!("{pre} Issue * {loops} : {}ms", duration.as_millis());

        let dat: &'static String = Box::leak(Box::new(dat));
        let mut payload = DatManager::_parse(&certificate, dat.clone().try_into()?)?;

        let start = Instant::now();
        if multi_thread {
            let mut futures: JoinSet<DatPayload> = JoinSet::new();
            for _ in 0..loops {
                futures.spawn(async move {
                    DatManager::_parse(&certificate, dat.clone().try_into().unwrap()).unwrap()
                });
            }
            while let Some(res) = futures.join_next().await {
                payload = res.unwrap();
            }
        } else {
            for _ in 0..loops {
                payload = DatManager::_parse(&certificate, dat.clone().try_into()?)?;
            }
        }
        let duration = start.elapsed();
        println!("{pre} Parse * {loops} : {}ms", duration.as_millis());

        let payload = payload.to_string_payload()?;
        assert_eq!(plain, &payload.plain);
        assert_eq!(secure, &payload.secure);
    }

    Ok(())
}

async fn benchmark(loop_size: i64) -> Result<(), DatError> {

    let plain: &'static String = Box::leak(Box::new(rand_string()));
    let secure: &'static String = Box::leak(Box::new(rand_string()));

    println!("performance test (plain, secure)");
    println!("plain: {}", plain);
    println!("secure: {}", secure);

    let mut certificates = Vec::with_capacity(6);

    for signature_algorithm in [P256, P384, P521] {
        for crypto_algorithm in [AES128GCMN, AES256GCMN] {
            certificates.push(DatCertificate::generate(
                0,
                signature_algorithm,
                crypto_algorithm,
                now_unix_timestamp() - 10,
                now_unix_timestamp() + 600,
                60
            )?)
        }
    }

    let certificates: &'static Vec<DatCertificate> = Box::leak(Box::new(certificates));

    loops(true, loop_size, &certificates, &plain, &secure).await?;
    loops(false, loop_size, &certificates, &plain, &secure).await?;

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test() {
    if cfg!(debug_assertions) {
        println!("performance test is disabled in debug mode.");
        return;
    }

    let loop_size = 10000;

    benchmark(loop_size).await.unwrap();
}
