use anyhow::{anyhow, Result};
use clap::builder::OsStr;
use sp_core::{
	crypto::{AccountId32, Pair as _, Ss58Codec},
	sr25519::Pair,
};
use std::str::FromStr;

#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NeededKeys {
	None,
	Public,
	Secret,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
pub enum SecretFormat {
	/// Raw 32B seed
	Seed,
	/// Substrate secret key or BIP39 mnemonic (optionally followed by derivation path)
	#[default]
	Substrate,
}

impl FromStr for SecretFormat {
	type Err = std::io::Error;

	fn from_str(s: &str) -> std::io::Result<Self> {
		match s {
			"seed" => Ok(SecretFormat::Seed),
			"substrate" => Ok(SecretFormat::Substrate),
			_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
		}
	}
}

impl From<SecretFormat> for &'static str {
	fn from(val: SecretFormat) -> &'static str {
		match val {
			SecretFormat::Seed => "seed",
			SecretFormat::Substrate => "substrate",
		}
	}
}

impl From<SecretFormat> for OsStr {
	fn from(val: SecretFormat) -> OsStr {
		OsStr::from(Into::<&str>::into(val))
	}
}

pub fn pair_from_str(secret_format: SecretFormat, secret: &str) -> Result<Pair> {
	match secret_format {
		SecretFormat::Seed => {
			let mut seed = [0; 32];
			hex::decode_to_slice(secret, &mut seed).map_err(|_| anyhow!("Invalid secret"))?;
			let pair = Pair::from_seed(&seed);
			Ok(pair)
		}
		SecretFormat::Substrate => {
			Pair::from_string(secret, None).map_err(|_| anyhow!("Invalid secret"))
		}
	}
}

pub fn prompt_secret(secret_format: SecretFormat) -> Pair {
	loop {
		match pair_from_str(
			secret_format,
			&rpassword::prompt_password(format!("Secret key ({secret_format:?}): ")).unwrap(),
		) {
			Ok(pair) => return pair,
			Err(_) => println!("Invalid secret"),
		}
	}
}

pub fn get_keys(
	secret_format: SecretFormat,
	address: &Option<String>,
	secret: &Option<String>,
	needed_keys: NeededKeys,
) -> Result<(Option<AccountId32>, Option<Pair>)> {
	// Get from args
	let mut account_id = match (address, secret) {
		(Some(address), Some(secret)) => {
			let pair = pair_from_str(secret_format, secret)?;
			let address = AccountId32::from_string(address)
				.map_err(|_| anyhow!("Invalid address {}", address))?;
			assert_eq!(
				address,
				pair.public().into(),
				"Secret and address do not match."
			);
			return Ok((Some(pair.public().into()), Some(pair)));
		}
		(None, Some(secret)) => {
			let pair = pair_from_str(secret_format, secret)?;
			return Ok((Some(pair.public().into()), Some(pair)));
		}
		(Some(address), None) => Some(
			AccountId32::from_str(address).map_err(|_| anyhow!("Invalid address {}", address))?,
		),
		(None, None) => None,
	};

	// Prompt
	if needed_keys == NeededKeys::Secret
		|| (account_id.is_none() && needed_keys == NeededKeys::Public)
	{
		loop {
			let pair = prompt_secret(secret_format);

			if let Some(account_id) = &account_id {
				if account_id != &pair.public().into() {
					println!("Secret and address do not match.");
				}
			} else {
				account_id = Some(pair.public().into());
				return Ok((account_id, Some(pair)));
			}
		}
	}

	Ok((account_id, None))
}