use anyhow::{anyhow, bail, Context, Result};
use eth_keystore::EthKeystore;
use fuels::accounts::wallet::DEFAULT_DERIVATION_PATH_PREFIX;
use home::home_dir;
use std::{
    fs,
    io::{Read, Write},
    path::{Path, PathBuf},
};
pub fn user_fuel_dir() -> PathBuf {
    const USER_FUEL_DIR: &str = ".fuel";
    let home_dir = home_dir().expect("failed to retrieve user home directory");
    home_dir.join(USER_FUEL_DIR)
}
pub fn user_fuel_wallets_dir() -> PathBuf {
    const WALLETS_DIR: &str = "wallets";
    user_fuel_dir().join(WALLETS_DIR)
}
pub fn user_fuel_wallets_accounts_dir() -> PathBuf {
    const ACCOUNTS_DIR: &str = "accounts";
    user_fuel_wallets_dir().join(ACCOUNTS_DIR)
}
pub fn default_wallet_path() -> PathBuf {
    const DEFAULT_WALLET_FILE_NAME: &str = ".wallet";
    user_fuel_wallets_dir().join(DEFAULT_WALLET_FILE_NAME)
}
pub fn load_wallet(wallet_path: &Path) -> Result<EthKeystore> {
    let file = fs::File::open(wallet_path).map_err(|e| {
        anyhow!(
            "Failed to load a wallet from {wallet_path:?}: {e}.\n\
            Please be sure to initialize a wallet before creating an account.\n\
            To initialize a wallet, use `forc-wallet init`"
        )
    })?;
    let reader = std::io::BufReader::new(file);
    serde_json::from_reader(reader).map_err(|e| {
        anyhow!(
            "Failed to deserialize keystore from {wallet_path:?}: {e}.\n\
            Please ensure that {wallet_path:?} is a valid wallet file."
        )
    })
}
pub(crate) fn wait_for_keypress() {
    let mut single_key = [0u8];
    std::io::stdin().read_exact(&mut single_key).unwrap();
}
pub(crate) fn get_derivation_path(account_index: usize) -> String {
    format!("{DEFAULT_DERIVATION_PATH_PREFIX}/{account_index}'/0/0")
}
pub(crate) fn request_new_password() -> String {
    let password =
        rpassword::prompt_password("Please enter a password to encrypt this private key: ")
            .unwrap();
    let confirmation = rpassword::prompt_password("Please confirm your password: ").unwrap();
    if password != confirmation {
        println!("Passwords do not match -- try again!");
        std::process::exit(1);
    }
    password
}
pub(crate) fn display_string_discreetly(
    discreet_string: &str,
    continue_message: &str,
) -> Result<()> {
    use termion::screen::IntoAlternateScreen;
    let mut screen = std::io::stdout().into_alternate_screen()?;
    writeln!(screen, "{discreet_string}")?;
    screen.flush()?;
    println!("{continue_message}");
    wait_for_keypress();
    Ok(())
}
pub(crate) fn write_wallet_from_mnemonic_and_password(
    wallet_path: &Path,
    mnemonic: &str,
    password: &str,
) -> Result<()> {
    if wallet_path.exists() {
        bail!(
            "File or directory already exists at {wallet_path:?}. \
            Remove the existing file, or provide a different path."
        );
    }
    let wallet_dir = wallet_path
        .parent()
        .ok_or_else(|| anyhow!("failed to retrieve parent directory of {wallet_path:?}"))?;
    std::fs::create_dir_all(wallet_dir)?;
    let wallet_file_name = wallet_path
        .file_name()
        .and_then(|os_str| os_str.to_str())
        .ok_or_else(|| anyhow!("failed to retrieve file name from {wallet_path:?}"))?;
    eth_keystore::encrypt_key(
        wallet_dir,
        &mut rand::thread_rng(),
        mnemonic,
        password,
        Some(wallet_file_name),
    )
    .with_context(|| format!("failed to create keystore at {wallet_path:?}"))
    .map(|_| ())
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::test_utils::{with_tmp_dir, TEST_MNEMONIC, TEST_PASSWORD};
    #[test]
    fn handle_absolute_path_argument() {
        with_tmp_dir(|tmp_dir| {
            let tmp_dir_abs = tmp_dir.canonicalize().unwrap();
            let wallet_path = tmp_dir_abs.join("wallet.json");
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            load_wallet(&wallet_path).unwrap();
        })
    }
    #[test]
    fn handle_relative_path_argument() {
        let wallet_path = Path::new("test-wallet.json");
        let panic = std::panic::catch_unwind(|| {
            write_wallet_from_mnemonic_and_password(wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            load_wallet(wallet_path).unwrap();
        });
        let _ = std::fs::remove_file(wallet_path);
        if let Err(e) = panic {
            std::panic::resume_unwind(e);
        }
    }
    #[test]
    fn derivation_path() {
        let derivation_path = get_derivation_path(0);
        assert_eq!(derivation_path, "m/44'/1179993420'/0'/0/0");
    }
    #[test]
    fn encrypt_and_save_phrase() {
        with_tmp_dir(|tmp_dir| {
            let wallet_path = tmp_dir.join("wallet.json");
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            let phrase_recovered = eth_keystore::decrypt_key(wallet_path, TEST_PASSWORD).unwrap();
            let phrase = String::from_utf8(phrase_recovered).unwrap();
            assert_eq!(phrase, TEST_MNEMONIC)
        });
    }
    #[test]
    fn write_wallet() {
        with_tmp_dir(|tmp_dir| {
            let wallet_path = tmp_dir.join("wallet.json");
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            load_wallet(&wallet_path).unwrap();
        })
    }
    #[test]
    #[should_panic]
    fn write_wallet_to_existing_file_should_fail() {
        with_tmp_dir(|tmp_dir| {
            let wallet_path = tmp_dir.join("wallet.json");
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
        })
    }
    #[test]
    fn write_wallet_subdir() {
        with_tmp_dir(|tmp_dir| {
            let wallet_path = tmp_dir.join("path").join("to").join("wallet");
            write_wallet_from_mnemonic_and_password(&wallet_path, TEST_MNEMONIC, TEST_PASSWORD)
                .unwrap();
            load_wallet(&wallet_path).unwrap();
        })
    }
}
#[cfg(test)]
pub(crate) mod test_utils {
    use super::*;
    use std::{panic, path::Path};
    pub(crate) const TEST_MNEMONIC: &str = "rapid mechanic escape victory bacon switch soda math embrace frozen novel document wait motor thrive ski addict ripple bid magnet horse merge brisk exile";
    pub(crate) const TEST_PASSWORD: &str = "1234";
    pub(crate) fn with_tmp_dir<F>(f: F)
    where
        F: FnOnce(&Path) + panic::UnwindSafe,
    {
        let tmp_dir_name = format!("forc-wallet-test-{:x}", rand::random::<u64>());
        let tmp_dir = user_fuel_dir().join(".tmp").join(tmp_dir_name);
        std::fs::create_dir_all(&tmp_dir).unwrap();
        let panic = panic::catch_unwind(|| f(&tmp_dir));
        std::fs::remove_dir_all(&tmp_dir).unwrap();
        if let Err(e) = panic {
            panic::resume_unwind(e);
        }
    }
    pub(crate) fn save_dummy_wallet_file(wallet_path: &Path) {
        write_wallet_from_mnemonic_and_password(wallet_path, TEST_MNEMONIC, TEST_PASSWORD).unwrap();
    }
    pub(crate) fn with_tmp_dir_and_wallet<F>(f: F)
    where
        F: FnOnce(&Path, &Path) + panic::UnwindSafe,
    {
        with_tmp_dir(|dir| {
            let wallet_path = dir.join("wallet.json");
            save_dummy_wallet_file(&wallet_path);
            f(dir, &wallet_path);
        })
    }
}