#![allow(clippy::derive_partial_eq_without_eq)]
use altair_runtime::constants::currency::{AIR, MILLI_AIR};
use cfg_primitives::{
	currency_decimals, parachains, AccountId, AuraId, Balance, BlockNumber, CFG, MILLI_CFG,
	SAFE_XCM_VERSION,
};
use cfg_types::{
	domain_address::DomainAddress,
	fee_keys::FeeKey,
	tokens::{usdc, AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata},
};
use cfg_utils::vec_to_fixed_array;
use cumulus_primitives_core::ParaId;
use hex_literal::hex;
use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};
use sc_service::{ChainType, Properties};
use serde::{Deserialize, Serialize};
use sp_core::{sr25519, Encode, Pair, Public};
use sp_runtime::{
	traits::{IdentifyAccount, Verify},
	FixedPointNumber,
};
use staging_xcm::{
	latest::{Location, NetworkId},
	prelude::{AccountKey20, GeneralIndex, GeneralKey, GlobalConsensus, PalletInstance, Parachain},
};
pub type AltairChainSpec =
	sc_service::GenericChainSpec<altair_runtime::RuntimeGenesisConfig, Extensions>;
pub type CentrifugeChainSpec =
	sc_service::GenericChainSpec<centrifuge_runtime::RuntimeGenesisConfig, Extensions>;
pub type DevelopmentChainSpec =
	sc_service::GenericChainSpec<development_runtime::RuntimeGenesisConfig, Extensions>;
use cfg_types::fixed_point::Rate;
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
	TPublic::Pair::from_string(&format!("//{seed}"), None)
		.expect("static values are valid; qed")
		.public()
}
#[derive(
	Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension,
)]
#[serde(deny_unknown_fields)]
pub struct Extensions {
	pub relay_chain: String,
	pub para_id: u32,
	pub first_evm_block: BlockNumber,
}
impl Extensions {
	pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Extensions> {
		sc_chain_spec::get_extension(chain_spec.extensions())
	}
}
fn development_extensions(para_id: u32) -> Extensions {
	Extensions {
		para_id,
		relay_chain: "rococo-local".into(),
		first_evm_block: 1,
	}
}
pub fn get_altair_session_keys(keys: AuraId) -> altair_runtime::SessionKeys {
	altair_runtime::SessionKeys {
		aura: keys.clone(),
		block_rewards: keys,
	}
}
pub fn get_centrifuge_session_keys(keys: AuraId) -> centrifuge_runtime::SessionKeys {
	centrifuge_runtime::SessionKeys {
		aura: keys.clone(),
		block_rewards: keys,
	}
}
pub fn get_development_session_keys(keys: AuraId) -> development_runtime::SessionKeys {
	development_runtime::SessionKeys {
		aura: keys.clone(),
		block_rewards: keys,
	}
}
type AccountPublic = <cfg_primitives::Signature as Verify>::Signer;
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
where
	AccountPublic: From<<TPublic::Pair as Pair>::Public>,
{
	AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
}
pub fn centrifuge_config() -> CentrifugeChainSpec {
	CentrifugeChainSpec::from_json_bytes(
		&include_bytes!("../res/genesis/centrifuge-genesis-spec-raw.json")[..],
	)
	.unwrap()
}
pub fn centrifuge_local(para_id: ParaId) -> CentrifugeChainSpec {
	let mut properties = Properties::new();
	properties.insert("tokenSymbol".into(), "DCFG".into());
	properties.insert("tokenDecimals".into(), currency_decimals::NATIVE.into());
	CentrifugeChainSpec::builder(
		centrifuge_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"),
		development_extensions(para_id.into()),
	)
	.with_name("Centrifuge Local")
	.with_id("centrifuge_local")
	.with_chain_type(ChainType::Local)
	.with_genesis_config_patch(centrifuge_genesis(
		vec![(
			get_account_id_from_seed::<sr25519::Public>("Alice"),
			get_from_seed::<AuraId>("Alice"),
		)],
		endowed_accounts(),
		endowed_evm_accounts(),
		Some(100000000 * CFG),
		para_id,
		council_members_bootstrap(),
	))
	.with_properties(properties)
	.build()
}
pub fn catalyst_config() -> CentrifugeChainSpec {
	CentrifugeChainSpec::from_json_bytes(&include_bytes!("../res/catalyst-spec-raw.json")[..])
		.unwrap()
}
pub fn altair_config() -> AltairChainSpec {
	AltairChainSpec::from_json_bytes(
		&include_bytes!("../res/genesis/altair-genesis-spec-raw.json")[..],
	)
	.unwrap()
}
pub fn demo_config() -> DevelopmentChainSpec {
	DevelopmentChainSpec::from_json_bytes(&include_bytes!("../res/demo-spec-raw.json")[..]).unwrap()
}
pub fn altair_local(para_id: ParaId) -> AltairChainSpec {
	let mut properties = Properties::new();
	properties.insert("tokenSymbol".into(), "DAIR".into());
	properties.insert("tokenDecimals".into(), currency_decimals::NATIVE.into());
	AltairChainSpec::builder(
		altair_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"),
		development_extensions(para_id.into()),
	)
	.with_name("Altair Local")
	.with_id("altair_local")
	.with_chain_type(ChainType::Local)
	.with_genesis_config_patch(altair_genesis(
		vec![(
			get_account_id_from_seed::<sr25519::Public>("Alice"),
			get_from_seed::<AuraId>("Alice"),
		)],
		endowed_accounts(),
		endowed_evm_accounts(),
		Some(100000000 * AIR),
		para_id,
		council_members_bootstrap(),
	))
	.with_properties(properties)
	.build()
}
pub fn development(para_id: ParaId) -> DevelopmentChainSpec {
	let mut properties = Properties::new();
	properties.insert("tokenSymbol".into(), "DEVEL".into());
	properties.insert("tokenDecimals".into(), currency_decimals::NATIVE.into());
	DevelopmentChainSpec::builder(
		development_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"),
		development_extensions(para_id.into()),
	)
	.with_name("Dev Live")
	.with_id("devel_live")
	.with_chain_type(ChainType::Live)
	.with_genesis_config_patch(development_genesis(
		get_account_id_from_seed::<sr25519::Public>("Alice"),
		vec![(
			get_account_id_from_seed::<sr25519::Public>("Alice"),
			get_from_seed::<AuraId>("Alice"),
		)],
		endowed_accounts(),
		endowed_evm_accounts(),
		Some(10000000 * CFG),
		para_id,
	))
	.with_properties(properties)
	.build()
}
fn endowed_accounts() -> Vec<AccountId> {
	vec![
		get_account_id_from_seed::<sr25519::Public>("Alice"),
		get_account_id_from_seed::<sr25519::Public>("Bob"),
		get_account_id_from_seed::<sr25519::Public>("Charlie"),
		get_account_id_from_seed::<sr25519::Public>("Dave"),
		get_account_id_from_seed::<sr25519::Public>("Eve"),
		get_account_id_from_seed::<sr25519::Public>("Ferdie"),
		get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
		get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
		get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
		get_account_id_from_seed::<sr25519::Public>("Dave//stash"),
		get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
		get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
	]
}
fn endowed_evm_accounts() -> Vec<([u8; 20], Option<u64>)> {
	vec![(
		hex!["7F429e2e38BDeFa7a2E797e3BEB374a3955746a4"],
		None,
	)]
}
fn council_members_bootstrap() -> Vec<AccountId> {
	endowed_accounts().into_iter().take(4).collect::<Vec<_>>()
}
fn centrifuge_genesis(
	initial_authorities: Vec<(AccountId, AuraId)>,
	mut endowed_accounts: Vec<AccountId>,
	endowed_evm_accounts: Vec<([u8; 20], Option<u64>)>,
	total_issuance: Option<Balance>,
	id: ParaId,
	council_members: Vec<AccountId>,
) -> serde_json::Value {
	let chain_id: u32 = id.into();
	endowed_accounts.extend(endowed_evm_accounts.into_iter().map(|(addr, id)| {
		let chain_id = id.unwrap_or(chain_id.into());
		DomainAddress::Evm(chain_id, addr.into()).account()
	}));
	let num_endowed_accounts = endowed_accounts.len();
	let balances = match total_issuance {
		Some(total_issuance) => {
			let balance_per_endowed = total_issuance
				.checked_div(num_endowed_accounts as Balance)
				.unwrap_or(0 as Balance);
			endowed_accounts
				.iter()
				.cloned()
				.map(|k| (k, balance_per_endowed))
				.collect::<Vec<_>>()
		}
		None => vec![],
	};
	serde_json::json!({
		"balances": { "balances": balances },
		"council": {
			"members": council_members,
		},
		"fees": {
			"initialFees": vec![(
				FeeKey::AnchorsCommit,
				2_365_296_803_653u128,
			)],
		},
		"parachainInfo": {
			"parachainId": id,
		},
		"collatorSelection": {
			"invulnerables": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"candidacyBond": 1 * CFG,
		},
		"session": {
			"keys": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, aura)| {
					(
						acc.clone(),                       acc,                               get_centrifuge_session_keys(aura), )
				})
				.collect::<Vec<_>>(),
		},
		"bridge": {
			"chains": vec![0],
			"resources": vec![
				(
					hex!["00000000000000000000000000000009e974040e705c10fb4de576d6cc261900"],
					hex!["50616c6c65744272696467652e7472616e73666572"].to_vec(),
				),
			],
			"relayers": vec![
				Into::<AccountId>::into(hex!["d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"]),
				Into::<AccountId>::into(hex!["c405224448dcd4259816b09cfedbd8df0e6796b16286ea18efa2d6343da5992e"]),
			],
			"threshold": 1,
		},
		"blockRewards": {
			"collators": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"collatorReward": 8_325 * MILLI_CFG,
			"treasuryInflationRate": Rate::saturating_from_rational(3, 100),
		},
		"evmChainId": {
			"chainId": Into::<u32>::into(chain_id),
		},
		"evm": {
			"accounts": runtime_common::evm::utils::account_genesis::<centrifuge_runtime::Precompiles>(),
		},
		"polkadotXcm": {
			"safeXcmVersion": Some(SAFE_XCM_VERSION),
		},
	})
}
fn altair_genesis(
	initial_authorities: Vec<(AccountId, AuraId)>,
	mut endowed_accounts: Vec<AccountId>,
	endowed_evm_accounts: Vec<([u8; 20], Option<u64>)>,
	total_issuance: Option<Balance>,
	id: ParaId,
	council_members: Vec<AccountId>,
) -> serde_json::Value {
	let chain_id: u32 = id.into();
	endowed_accounts.extend(endowed_evm_accounts.into_iter().map(|(addr, id)| {
		let chain_id = id.unwrap_or(chain_id.into());
		DomainAddress::Evm(chain_id, addr.into()).account()
	}));
	let num_endowed_accounts = endowed_accounts.len();
	let balances = match total_issuance {
		Some(total_issuance) => {
			let balance_per_endowed = total_issuance
				.checked_div(num_endowed_accounts as Balance)
				.unwrap_or(0 as Balance);
			endowed_accounts
				.iter()
				.cloned()
				.map(|k| (k, balance_per_endowed))
				.collect::<Vec<_>>()
		}
		None => vec![],
	};
	serde_json::json!({
		"balances": { "balances": balances },
		"council": {
			"members": council_members.clone(),
		},
		"fees": {
			"initialFees": vec![(
				FeeKey::AnchorsCommit,
				2_365_296_803_653u128,
			)],
		},
		"parachainInfo": {
			"parachainId": id,
		},
		"collatorSelection": {
			"invulnerables": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"candidacyBond": 1 * AIR,
		},
		"blockRewards": {
			"collators": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"collatorReward": 98_630 * MILLI_AIR,
			"treasuryInflationRate": Rate::saturating_from_rational(3, 100),
		},
		"session": {
			"keys": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, aura)| {
					(
						acc.clone(),                   acc,                           get_altair_session_keys(aura), )
				})
				.collect::<Vec<_>>(),
		},
		"evmChainId": {
			"chainId": Into::<u32>::into(chain_id),
		},
		"evm": {
			"accounts": runtime_common::evm::utils::account_genesis::<altair_runtime::Precompiles>(),
		},
		"polkadotXcm": {
			"safeXcmVersion": Some(SAFE_XCM_VERSION),
		},
		"technicalCommittee": {
			"members": council_members
		}
	})
}
const DEV_USDT_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1);
const DEV_AUSD_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(2);
fn development_genesis(
	root_key: AccountId,
	initial_authorities: Vec<(AccountId, AuraId)>,
	mut endowed_accounts: Vec<AccountId>,
	endowed_evm_accounts: Vec<([u8; 20], Option<u64>)>,
	total_issuance: Option<Balance>,
	id: ParaId,
) -> serde_json::Value {
	let chain_id: u32 = id.into();
	endowed_accounts.extend(endowed_evm_accounts.into_iter().map(|(addr, id)| {
		let chain_id = id.unwrap_or(chain_id.into());
		DomainAddress::Evm(chain_id, addr.into()).account()
	}));
	let num_endowed_accounts = endowed_accounts.len();
	let (balances, token_balances) = match total_issuance {
		Some(total_issuance) => {
			let balance_per_endowed = total_issuance
				.checked_div(num_endowed_accounts as Balance)
				.unwrap_or(0 as Balance);
			(
				endowed_accounts
					.iter()
					.cloned()
					.map(|x| (x, balance_per_endowed))
					.collect::<Vec<_>>(),
				endowed_accounts
					.iter()
					.cloned()
					.flat_map(|x| {
						vec![
							(x.clone(), DEV_USDT_CURRENCY_ID, 1_000_000_000_000u128),
							(x, DEV_AUSD_CURRENCY_ID, 1_000_000_000_000_000_000u128),
						]
					})
					.collect::<Vec<_>>(),
			)
		}
		None => (vec![], vec![]),
	};
	let chain_id: u32 = id.into();
	serde_json::json!({
		"balances": { "balances": balances },
		"ormlAssetRegistry": {
			"assets": asset_registry_assets(),
		},
		"ormlTokens": {
			"balances": token_balances,
		},
		"fees": {
			"initialFees": vec![(
				FeeKey::AnchorsCommit,
				2_365_296_803_653u128,
			)],
		},
		"sudo": {
			"key": Some(root_key),
		},
		"parachainInfo":  {
			"parachainId": id,
		},
		"collatorSelection": {
			"invulnerables": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"candidacyBond": 1 * CFG,
		},
		"session": {
			"keys": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, aura)| {
					(
						acc.clone(),                        acc,                                get_development_session_keys(aura), )
				})
				.collect::<Vec<_>>(),
		},
		"bridge": {
			"chains": vec![0],
			"resources": vec![
				(
					hex!["00000000000000000000000000000009e974040e705c10fb4de576d6cc261900"],
					hex!["50616c6c65744272696467652e7472616e73666572"].to_vec(),
				),
			],
			"relayers": vec![
				Into::<AccountId>::into(hex!["d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"]),
				Into::<AccountId>::into(hex!["c405224448dcd4259816b09cfedbd8df0e6796b16286ea18efa2d6343da5992e"]),
			],
			"threshold": 1,
		},
		"blockRewards": {
			"collators": initial_authorities
				.iter()
				.cloned()
				.map(|(acc, _)| acc)
				.collect::<Vec<_>>(),
			"collatorReward": 8_325 * MILLI_CFG,
			"treasuryInflationRate": Rate::saturating_from_rational(3, 100),
		},
		"evmChainId": {
			"chainId": Into::<u32>::into(chain_id),
		},
		"evm": {
			"accounts": runtime_common::evm::utils::account_genesis::<development_runtime::Precompiles>(),
		},
		"polkadotXcm": {
			"safeXcmVersion": Some(SAFE_XCM_VERSION),
		},
	})
}
fn asset_registry_assets() -> Vec<(CurrencyId, Vec<u8>)> {
	vec![
		(
			DEV_USDT_CURRENCY_ID,
			AssetMetadata {
				decimals: 6,
				name: b"Tether USD"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				symbol: b"USDT".to_vec().try_into().expect("fit in the BoundedVec"),
				existential_deposit: 0u128,
				location: Some(staging_xcm::VersionedLocation::V4(Location::new(
					1,
					[
						Parachain(parachains::rococo::rocksmine::ID),
						PalletInstance(parachains::rococo::rocksmine::usdt::PALLET_INSTANCE),
						GeneralIndex(parachains::rococo::rocksmine::usdt::GENERAL_INDEX),
					],
				))),
				additional: CustomMetadata {
					mintable: false,
					permissioned: false,
					pool_currency: true,
					transferability: CrossChainTransferability::Xcm(Default::default()),
					local_representation: None,
				},
			}
			.encode(),
		),
		(
			DEV_AUSD_CURRENCY_ID,
			AssetMetadata {
				decimals: 12,
				name: b"Acala USD"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				symbol: b"AUSD".to_vec().try_into().expect("fit in the BoundedVec"),
				existential_deposit: 0u128,
				location: Some(staging_xcm::VersionedLocation::V4(Location::new(
					1,
					[
						Parachain(parachains::rococo::acala::ID),
						GeneralKey {
							length: parachains::rococo::acala::AUSD_KEY.to_vec().len() as u8,
							data: vec_to_fixed_array(parachains::rococo::acala::AUSD_KEY),
						},
					],
				))),
				additional: CustomMetadata {
					mintable: false,
					permissioned: false,
					pool_currency: true,
					transferability: CrossChainTransferability::Xcm(Default::default()),
					local_representation: None,
				},
			}
			.encode(),
		),
		(
			usdc::CURRENCY_ID_LOCAL,
			AssetMetadata {
				decimals: 6,
				name: b"Local USDC"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				symbol: b"localUSDC"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				existential_deposit: 0u128,
				location: None,
				additional: CustomMetadata {
					mintable: false,
					permissioned: false,
					pool_currency: true,
					transferability: CrossChainTransferability::None,
					local_representation: None,
				},
			}
			.encode(),
		),
		(
			usdc::CURRENCY_ID_LP_ETH_GOERLI,
			AssetMetadata {
				decimals: usdc::DECIMALS,
				name: b"LP Ethereum Wrapped USDC"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				symbol: b"LpEthUSDC"
					.to_vec()
					.try_into()
					.expect("fit in the BoundedVec"),
				existential_deposit: usdc::EXISTENTIAL_DEPOSIT,
				location: Some(staging_xcm::VersionedLocation::V4(Location::new(
					0,
					[
						PalletInstance(development_runtime::LiquidityPoolsPalletIndex::get()),
						GlobalConsensus(NetworkId::Ethereum {
							chain_id: usdc::CHAIN_ID_ETH_GOERLI_TESTNET,
						}),
						AccountKey20 {
							network: None,
							key: usdc::CONTRACT_ETH_GOERLI,
						},
					],
				))),
				additional: CustomMetadata {
					transferability: CrossChainTransferability::LiquidityPools,
					mintable: false,
					permissioned: false,
					pool_currency: true,
					local_representation: Some(usdc::LOCAL_ASSET_ID),
				},
			}
			.encode(),
		),
	]
}