use core::str::FromStr;
use eyre::eyre;
use hdpath::StandardHDPath;
use serde_json as json;
use std::fs;
use std::path::PathBuf;
use std::str;
use toml;
use tracing::debug;
use ibc_relayer::keyring::{Secp256k1KeyPair, SigningKeyPair};
use crate::chain::cli::bootstrap::{
    add_genesis_account, add_genesis_validator, add_wallet, collect_gen_txs, initialize,
    start_chain,
};
use crate::chain::driver::ChainDriver;
use crate::error::{handle_generic_error, Error};
use crate::ibc::token::Token;
use crate::types::process::ChildProcess;
use crate::types::wallet::{Wallet, WalletAddress, WalletId};
pub trait ChainBootstrapMethodsExt {
    fn read_file(&self, file_path: &str) -> Result<String, Error>;
    fn write_file(&self, file_path: &str, content: &str) -> Result<(), Error>;
    fn update_chain_config(
        &self,
        file: &str,
        cont: impl FnOnce(&mut toml::Value) -> Result<(), Error>,
    ) -> Result<(), Error>;
    fn initialize(&self) -> Result<(), Error>;
    fn update_genesis_file(
        &self,
        file: &str,
        cont: impl FnOnce(&mut serde_json::Value) -> Result<(), Error>,
    ) -> Result<(), Error>;
    fn add_wallet(&self, wallet_id: &str) -> Result<Wallet, Error>;
    fn add_genesis_account(&self, wallet: &WalletAddress, amounts: &[&Token]) -> Result<(), Error>;
    fn add_genesis_validator(&self, wallet_id: &WalletId, token: &Token) -> Result<(), Error>;
    fn collect_gen_txs(&self) -> Result<(), Error>;
    fn start(&self) -> Result<ChildProcess, Error>;
}
impl ChainBootstrapMethodsExt for ChainDriver {
    fn read_file(&self, file_path: &str) -> Result<String, Error> {
        let full_path = PathBuf::from(&self.home_path).join(file_path);
        let res = fs::read_to_string(full_path)?;
        Ok(res)
    }
    fn write_file(&self, file_path: &str, content: &str) -> Result<(), Error> {
        let full_path = PathBuf::from(&self.home_path).join(file_path);
        let full_path_str = format!("{}", full_path.display());
        fs::write(full_path, content)?;
        debug!("created new file {:?}", full_path_str);
        Ok(())
    }
    fn update_chain_config(
        &self,
        file: &str,
        cont: impl FnOnce(&mut toml::Value) -> Result<(), Error>,
    ) -> Result<(), Error> {
        let config_path = format!("config/{file}");
        let config1 = self.read_file(&config_path)?;
        let mut config2 = toml::from_str(&config1).map_err(handle_generic_error)?;
        cont(&mut config2)?;
        let config3 = toml::to_string_pretty(&config2).map_err(handle_generic_error)?;
        self.write_file(&config_path, &config3)?;
        Ok(())
    }
    fn initialize(&self) -> Result<(), Error> {
        initialize(self.chain_id.as_str(), &self.command_path, &self.home_path)
    }
    fn update_genesis_file(
        &self,
        file: &str,
        cont: impl FnOnce(&mut serde_json::Value) -> Result<(), Error>,
    ) -> Result<(), Error> {
        let config1 = self.read_file(&format!("config/{file}"))?;
        let mut config2 = serde_json::from_str(&config1).map_err(handle_generic_error)?;
        cont(&mut config2)?;
        let config3 = serde_json::to_string_pretty(&config2).map_err(handle_generic_error)?;
        self.write_file("config/genesis.json", &config3)?;
        Ok(())
    }
    fn add_wallet(&self, wallet_id: &str) -> Result<Wallet, Error> {
        let seed_content = add_wallet(
            self.chain_id.as_str(),
            &self.command_path,
            &self.home_path,
            wallet_id,
        )?;
        let json_val: json::Value = json::from_str(&seed_content).map_err(handle_generic_error)?;
        let wallet_address = json_val
            .get("address")
            .ok_or_else(|| eyre!("expect address string field to be present in json result"))?
            .as_str()
            .ok_or_else(|| eyre!("expect address string field to be present in json result"))?
            .to_string();
        let seed_path = format!("{wallet_id}-seed.json");
        self.write_file(&seed_path, &seed_content)?;
        let hd_path = StandardHDPath::from_str(self.chain_type.hd_path())
            .map_err(|e| eyre!("failed to create StandardHDPath: {:?}", e))?;
        let key = Secp256k1KeyPair::from_seed_file(&seed_content, &hd_path)
            .map_err(handle_generic_error)?;
        Ok(Wallet::new(wallet_id.to_string(), wallet_address, key))
    }
    fn add_genesis_account(&self, wallet: &WalletAddress, amounts: &[&Token]) -> Result<(), Error> {
        let amounts_str = amounts.iter().map(|t| t.to_string()).collect::<Vec<_>>();
        add_genesis_account(
            self.chain_id.as_str(),
            &self.command_path,
            &self.home_path,
            &wallet.0,
            &amounts_str,
        )
    }
    fn add_genesis_validator(&self, wallet_id: &WalletId, token: &Token) -> Result<(), Error> {
        add_genesis_validator(
            self.chain_id.as_str(),
            &self.command_path,
            &self.home_path,
            &wallet_id.0,
            &token.to_string(),
        )
    }
    fn collect_gen_txs(&self) -> Result<(), Error> {
        collect_gen_txs(self.chain_id.as_str(), &self.command_path, &self.home_path)
    }
    fn start(&self) -> Result<ChildProcess, Error> {
        let extra_start_args = self.chain_type.extra_start_args();
        start_chain(
            &self.command_path,
            &self.home_path,
            &self.rpc_listen_address(),
            &self.grpc_listen_address(),
            &extra_start_args
                .iter()
                .map(|s| s.as_ref())
                .collect::<Vec<_>>(),
        )
    }
}