use sc_cli::{
    utils::{PublicFor, SeedFor},
    OutputType,
};
use serde_json::json;
use sp_core::{
    crypto::{
        unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
    },
    hexdisplay::HexDisplay,
};
use sp_runtime::{traits::IdentifyAccount, MultiSigner};

pub fn print_from_uri<Pair>(
    uri: &str,
    password: Option<SecretString>,
    network_override: Option<Ss58AddressFormat>,
    output: OutputType,
) where
    Pair: sp_core::Pair,
    Pair::Public: Into<MultiSigner>,
{
    let password = password.as_ref().map(|s| s.expose_secret().as_str());
    let network_id = unwrap_or_default_ss58_name(network_override);

    if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
        let public_key = pair.public();
        let network_override = unwrap_or_default_ss58_version(network_override);

        match output {
            OutputType::Json => {
                let json = json!({
                    "secretPhrase": uri,
                    "networkId": network_id,
                    "secretSeed": format_seed::<Pair>(seed),
                    "publicKey": format_public_key::<Pair>(public_key.clone()),
                    "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
                    "accountId": format_account_id::<Pair>(public_key),
                    "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
                });
                println!(
                    "{}",
                    serde_json::to_string_pretty(&json).expect("Json pretty print failed")
                );
            }
            OutputType::Text => {
                println!(
                    "Secret phrase:       {}\n  \
					Network ID:        {}\n  \
					Secret seed:       {}\n  \
					Public key (hex):  {}\n  \
					Account ID:        {}\n  \
					Public key (SS58): {}\n  \
					SS58 Address:      {}",
                    uri,
                    network_id,
                    format_seed::<Pair>(seed),
                    format_public_key::<Pair>(public_key.clone()),
                    format_account_id::<Pair>(public_key.clone()),
                    public_key.to_ss58check_with_version(network_override),
                    pair.public()
                        .into()
                        .into_account()
                        .to_ss58check_with_version(network_override),
                );
            }
        }
    } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
        let public_key = pair.public();
        let network_override = unwrap_or_default_ss58_version(network_override);

        match output {
            OutputType::Json => {
                let json = json!({
                    "secretKeyUri": uri,
                    "networkId": network_id,
                    "secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
                    "publicKey": format_public_key::<Pair>(public_key.clone()),
                    "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
                    "accountId": format_account_id::<Pair>(public_key),
                    "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
                });
                println!(
                    "{}",
                    serde_json::to_string_pretty(&json).expect("Json pretty print failed")
                );
            }
            OutputType::Text => {
                println!(
                    "Secret Key URI `{}` is account:\n  \
					Network ID:        {}\n  \
					Secret seed:       {}\n  \
					Public key (hex):  {}\n  \
					Account ID:        {}\n  \
					Public key (SS58): {}\n  \
					SS58 Address:      {}",
                    uri,
                    network_id,
                    if let Some(seed) = seed {
                        format_seed::<Pair>(seed)
                    } else {
                        "n/a".into()
                    },
                    format_public_key::<Pair>(public_key.clone()),
                    format_account_id::<Pair>(public_key.clone()),
                    public_key.to_ss58check_with_version(network_override),
                    pair.public()
                        .into()
                        .into_account()
                        .to_ss58check_with_version(network_override),
                );
            }
        }
    } else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
        let network_override = network_override.unwrap_or(network);

        match output {
            OutputType::Json => {
                let json = json!({
                    "publicKeyUri": uri,
                    "networkId": String::from(network_override),
                    "publicKey": format_public_key::<Pair>(public_key.clone()),
                    "accountId": format_account_id::<Pair>(public_key.clone()),
                    "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
                    "ss58Address": public_key.to_ss58check_with_version(network_override),
                });

                println!(
                    "{}",
                    serde_json::to_string_pretty(&json).expect("Json pretty print failed")
                );
            }
            OutputType::Text => {
                println!(
                    "Public Key URI `{}` is account:\n  \
					 Network ID/Version: {}\n  \
					 Public key (hex):   {}\n  \
					 Account ID:         {}\n  \
					 Public key (SS58):  {}\n  \
					 SS58 Address:       {}",
                    uri,
                    String::from(network_override),
                    format_public_key::<Pair>(public_key.clone()),
                    format_account_id::<Pair>(public_key.clone()),
                    public_key.to_ss58check_with_version(network_override),
                    public_key.to_ss58check_with_version(network_override),
                );
            }
        }
    } else {
        println!("Invalid phrase/URI given");
    }
}

/// Try to parse given `public` as hex encoded public key and print relevant information.
pub fn print_from_public<Pair>(
    public_str: &str,
    network_override: Option<Ss58AddressFormat>,
    output: OutputType,
) -> Result<(), sc_cli::Error>
where
    Pair: sp_core::Pair,
    Pair::Public: Into<MultiSigner>,
{
    let public = array_bytes::hex2bytes(public_str)?;

    let public_key = Pair::Public::try_from(&public)
        .map_err(|_| "Failed to construct public key from given hex")?;

    let network_override = unwrap_or_default_ss58_version(network_override);

    match output {
        OutputType::Json => {
            let json = json!({
                "networkId": String::from(network_override),
                "publicKey": format_public_key::<Pair>(public_key.clone()),
                "accountId": format_account_id::<Pair>(public_key.clone()),
                "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
                "ss58Address": public_key.to_ss58check_with_version(network_override),
            });

            println!(
                "{}",
                serde_json::to_string_pretty(&json).expect("Json pretty print failed")
            );
        }
        OutputType::Text => {
            println!(
                "Network ID/Version: {}\n  \
				 Public key (hex):   {}\n  \
				 Account ID:         {}\n  \
				 Public key (SS58):  {}\n  \
				 SS58 Address:       {}",
                String::from(network_override),
                format_public_key::<Pair>(public_key.clone()),
                format_account_id::<Pair>(public_key.clone()),
                public_key.to_ss58check_with_version(network_override),
                public_key.to_ss58check_with_version(network_override),
            );
        }
    }

    Ok(())
}

pub fn unwrap_or_default_ss58_name(x: Option<Ss58AddressFormat>) -> String {
    if let Some(x) = x {
        let name = match x.prefix() {
            1995 => String::from("Ghost"),
            1996 => String::from("Casper"),
            _ => String::from("Unwknown"),
        };
        format!("{} ({})", name, x.prefix())
    } else {
        String::from("Unknown (unknown)")
    }
}

pub fn format_seed<P: sp_core::Pair>(seed: SeedFor<P>) -> String {
    format!("0x{}", HexDisplay::from(&seed.as_ref()))
}

fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
    format!("0x{}", HexDisplay::from(&public_key.as_ref()))
}

fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where
    PublicFor<P>: Into<MultiSigner>,
{
    format!(
        "0x{}",
        HexDisplay::from(&public_key.into().into_account().as_ref())
    )
}
