use keccak_hash::keccak_256;
use secp256k1::{PublicKey, Secp256k1};
use std::{
    sync::{Arc, Mutex},
    thread,
};

fn main() {
    let args = std::env::args();
    if args.len() <= 1 {
        println!("[ERROR] Need at least one argument, e.g. 0xff 0xffff ...");
        return;
    }
    let continue_on_found = std::env::var("CONTINUE_ON_FOUND")
        .map(|c| c.to_lowercase())
        .map(|c| c == "1" || c == "true" || c == "yes" || c == "on")
        .unwrap_or_else(|_| false);

    let prefixes: Vec<String> = args.skip(1).collect();
    println!("Prefixes: {:?}", prefixes);
    if continue_on_found {
        println!("CONTINUE_ON_FOUND is ON");
    } else {
        println!("CONTINUE_ON_FOUND is OFF");
    }
    let loop_count = std::env::var("LOOP_COUNT")
        .map(|c| c.parse::<u64>())
        .unwrap_or_else(|_| Ok(10_000_000_u64))
        .unwrap_or_else(|_| 10_000_000_u64);
    println!("LOOP_COUNT={}", loop_count);

    let num_of_vcpus = num_cpus::get();
    let num_of_phycpus = num_cpus::get_physical();
    println!(
        "You have {} vCPUs, from {} phyCPUs",
        num_of_vcpus, num_of_phycpus
    );
    let concurrent_count = std::env::var("CONCURRENT_COUNT")
        .map(|c| c.parse::<u64>())
        .unwrap_or_else(|_| Ok(num_of_phycpus as u64))
        .unwrap_or_else(|_| 1_u64);
    println!("CONCURRENT_COUNT={}", concurrent_count);

    let stop_flag = Arc::new(Mutex::new(false));
    let mut handles = vec![];
    for ind in 0..concurrent_count {
        println!("- Running task {} of {}", ind, concurrent_count);
        let the_stop_flag = Arc::clone(&stop_flag);
        let prefixes = prefixes.clone();
        let child = thread::spawn(move || {
            run_one_task(prefixes, continue_on_found, loop_count, ind, the_stop_flag);
        });
        handles.push(child);
    }
    while let Some(h) = handles.pop() {
        h.join().unwrap();
    }
    println!("Finished!");
}

fn run_one_task(
    prefixes: Vec<String>,
    continue_on_found: bool,
    loop_count: u64,
    ind: u64,
    the_stop_flag: Arc<Mutex<bool>>,
) {
    let secp = Secp256k1::new();

    for i in 0..loop_count {
        let (secret_key, public_key) = secp.generate_keypair(&mut rand::rng());

        let s = make_eth_address(&public_key);

        if i % 1_000 == 0 {
            if *the_stop_flag.lock().unwrap() {
                return;
            }
            if i > 0 && i % 100_000 == 0 {
                println!("> {} - {}", ind, i);
            }
        }
        let lower_s = s.to_ascii_lowercase();
        if prefixes.iter().any(|p| lower_s.starts_with(p)) {
            println!(">> {}\n{}", s, hex::encode(secret_key.as_ref()));

            if !continue_on_found {
                *the_stop_flag.lock().unwrap() = true;
                break;
            }
        }
    }
}

// reference: https://eips.ethereum.org/EIPS/eip-55
fn make_eth_address(public_key: &PublicKey) -> String {
    let public_key_bytes = public_key.serialize_uncompressed().to_vec();

    let mut output = [0_u8; 32];
    keccak_256(&public_key_bytes[1..], &mut output);

    let addr_without_prefix_0x = hex::encode(&output[32 - 20..]);
    let mut addr_keccak_256_output = [0_u8; 32];
    keccak_256(
        addr_without_prefix_0x.as_bytes(),
        &mut addr_keccak_256_output,
    );
    let addr_keccak_256_hex = hex::encode(&addr_keccak_256_output);

    let mut addr = String::with_capacity(42);
    addr.push_str("0x");
    let addr_keccak_256_chars = addr_keccak_256_hex.chars().collect::<Vec<_>>();
    let addr_chars = addr_without_prefix_0x.chars().collect::<Vec<_>>();
    for i in 0..addr_chars.len() {
        if addr_keccak_256_chars[i] >= '8' {
            addr.push(addr_chars[i].to_ascii_uppercase());
        } else {
            addr.push(addr_chars[i])
        }
    }
    addr
}

#[test]
fn test_make_eth_address() {
    use secp256k1::{SecretKey};
    
    for (private_key, eth_address) in [
        (
            "961fc1985ceca1e23b169103e041ecfa642ff8690a6ecc59d91f2dba648ab02b",
            "0x0005119dEE1fD8763fE6ADBbc801484a3728F0db",
        ),
        (
            "2fa0db47c5939d7d4831d05dcdb329f6e7f7d5ca2c7247a666729aa513ea26bf",
            "0x23aEf078e0997434Eb47E6d0B3C7770010473456",
        ),
        (
            "e6a8f02f9d308eca44b8437d6dc1d08101449ecef5851dfe5093bcf0bc21fa5b",
            "0xef87E9712c412ef7B90804EbaB763FAe24E10123",
        ),
        (
            "3d541861865959e1e1c303d5c2a90084192415b61ead3fdb778047278011170b",
            "0x1451449D05A3C232a543A995C2A38c07095f6666",
        ),
    ] {
        let temp_key = hex::decode(private_key).unwrap();
        let mut key_32_bytes = [0_u8; 32];
        for i in 0..32 {
            key_32_bytes[i] = temp_key[i];
        }
        let secp = Secp256k1::new();
        let secret_key =
            SecretKey::from_byte_array(key_32_bytes).expect("32 bytes, within curve order");
        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
        assert_eq!(eth_address, make_eth_address(&public_key));
    }
}
