//! Ghost chain configuration.

use codec::Encode;

use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId;
use babe_primitives::AuthorityId as BabeId;
use ghost_slow_clap::sr25519::AuthorityId as SlowClapId;
use grandpa_primitives::AuthorityId as GrandpaId;
#[cfg(feature = "casper-native")]
use pallet_staking::Forcing;
use primitives::{AccountId, AccountPublic};

use crate::legacy::legacy_endowments;

#[cfg(feature = "casper-native")]
use casper_runtime as casper;
#[cfg(feature = "casper-native")]
use casper_runtime_constants::currency::{CSPR, STRH};
use sc_chain_spec::ChainSpecExtension;
#[cfg(feature = "casper-native")]
use sc_chain_spec::ChainType;
use serde::{Deserialize, Serialize};
use sp_core::{sr25519, Pair, Public};
use sp_runtime::traits::IdentifyAccount;
#[cfg(feature = "casper-native")]
use sp_runtime::Perbill;
#[cfg(feature = "casper-native")]
use telemetry::TelemetryEndpoints;

#[cfg(feature = "casper-native")]
const CASPER_TELEMETRY_URL: &str = "wss://telemetry.ghostchain.io/submit/";

#[cfg(feature = "casper-native")]
const DEFAULT_PROTOCOL_ID: &str = "cspr";

/// Node `ChainSpec` extensions.
///
/// Additional parameters for some substrate core modules,
/// customizable from the chain spec.
#[derive(Default, Clone, Serialize, Deserialize, ChainSpecExtension)]
#[serde(rename_all = "camelCase")]
pub struct Extensions {
    /// Block number with known hashes.
    pub fork_blocks: sc_client_api::ForkBlocks<primitives::Block>,
    /// Known bad block hashes.
    pub bad_blocks: sc_client_api::BadBlocks<primitives::Block>,
    /// The light sync state.
    /// This value will be set by the `sync-state rpc` implementation.
    pub light_sync_state: sc_sync_state_rpc::LightSyncStateExtension,
}

// Generic chain spec, in case when we don't have the native runtime.
pub type GenericChainSpec = sc_service::GenericChainSpec<(), Extensions>;

/// The `ChainSpec` parametrized for the ghost runtime.
#[cfg(feature = "casper-native")]
pub type CasperChainSpec = sc_service::GenericChainSpec<(), Extensions>;

#[cfg(not(feature = "casper-native"))]
pub type CasperChainSpec = GenericChainSpec;

pub fn casper_config() -> Result<CasperChainSpec, String> {
    CasperChainSpec::from_json_bytes(&include_bytes!("../chain-specs/casper.json")[..])
}

#[cfg(feature = "casper-native")]
fn casper_session_keys(
    babe: BabeId,
    grandpa: GrandpaId,
    authority_discovery: AuthorityDiscoveryId,
    slow_clap: SlowClapId,
) -> casper::opaque::SessionKeys {
    casper::opaque::SessionKeys {
        babe,
        grandpa,
        authority_discovery,
        slow_clap,
    }
}

pub fn casper_chain_spec_properties() -> serde_json::map::Map<String, serde_json::Value> {
    serde_json::json!({
        "ss58Format": 1996,
        "tokenDecimals": 18,
        "tokenSymbol": "CSPR",
    })
    .as_object()
    .expect("Map given; qed")
    .clone()
}

/// Helper function to generate a crypto pair from seed.
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()
}

/// Helper function to generate account ID from seed.
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()
}

/// Helper function to generate stash, controller and session key from seed
pub fn generate_authority_keys_from_seed(
    seed: &str,
) -> (
    AccountId,
    AccountId,
    BabeId,
    GrandpaId,
    AuthorityDiscoveryId,
    SlowClapId,
) {
    let keys = get_authority_keys_from_seed(seed);
    (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5)
}

/// Helper function to generate stash, controller and session key from seed
pub fn get_authority_keys_from_seed(
    seed: &str,
) -> (
    AccountId,
    AccountId,
    BabeId,
    GrandpaId,
    AuthorityDiscoveryId,
    SlowClapId,
) {
    (
        get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)),
        get_account_id_from_seed::<sr25519::Public>(seed),
        get_from_seed::<BabeId>(seed),
        get_from_seed::<GrandpaId>(seed),
        get_from_seed::<AuthorityDiscoveryId>(seed),
        get_from_seed::<SlowClapId>(seed),
    )
}

#[cfg(feature = "casper-native")]
fn casper_testnet_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>("Feride"),
        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>("Feride//stash"),
    ]
}

#[cfg(feature = "casper-native")]
fn casper_testnet_evm_accounts() -> Vec<(AccountId, u128, u8)> {
    vec![
        // 01c928771aea942a1e7ac06adf2b73dfbc9a25d9eaa516e3673116af7f345198
        (
            get_account_id_from_seed::<sr25519::Public>("1A69d2D5568D1878023EeB121a73d33B9116A760"),
            1337 * CSPR,
            1,
        ),
        // b19a435901872f817185f7234a1484eae837613f9d10cf21927a23c2d8cb9139
        (
            get_account_id_from_seed::<sr25519::Public>("2f86cfBED3fbc1eCf2989B9aE5fc019a837A9C12"),
            1337 * CSPR,
            2,
        ),
        // d3baf57b74d65719b2dc33f5a464176022d0cc5edbca002234229f3e733875fc
        (
            get_account_id_from_seed::<sr25519::Public>("e83f67361Ac74D42A48E2DAfb6706eb047D8218D"),
            69 * CSPR,
            3,
        ),
        // c4683d566436af6b58b4a59c8f501319226e85b21869bf93d5eeb4596d4791d4
        (
            get_account_id_from_seed::<sr25519::Public>("827ee4ad9b259b6fa1390ed60921508c78befd63"),
            69 * CSPR,
            4,
        ),
    ]
}

#[cfg(feature = "casper-native")]
fn casper_testnet_evm_networks() -> Vec<(u32, Vec<u8>, u128)> {
    vec![
        (
            11155111,
            ghost_networks::NetworkData {
                chain_name: "sepolia-ethereum-testnet".into(),
                default_endpoints: vec![
                    "https://sepolia.drpc.org".into(),
                    "https://sepolia.gateway.tenderly.co".into(),
                    "https://api.zan.top/eth-sepolia".into(),
                    "https://rpc.sepolia.ethpandaops.io".into(),
                    "https://ethereum-sepolia-rpc.publicnode.com".into(),
                    "https://1rpc.io/sepolia".into(),
                    "https://0xrpc.io/sep".into(),
                    "https://eth-sepolia.api.onfinality.io/public".into(),
                ],
                finality_delay: 69u64,
                rate_limit_delay: 5_000u64,
                block_distance: 20u64,
                avg_block_speed: 12_000,
                network_type: ghost_networks::NetworkType::Evm,
                gatekeeper: "0xc85129A097773B7F8970a7364c928C05f265E6A1".into(),
                topic_name: "0x7ab52ec05c331e6257a3d705d6bea6e4c27277351764ad139209e06b203811a6"
                    .into(),
                incoming_fee: 69_000_000u32,
                outgoing_fee: 0u32,
            }
            .encode(),
            2048861035254140036511u128,
        ),
        (
            63,
            ghost_networks::NetworkData {
                chain_name: "mordor-classic-testnet".into(),
                default_endpoints: vec![
                    "https://rpc.mordor.etccooperative.org".into(),
                    "https://geth-mordor.etc-network.info".into(),
                    "https://0xrpc.io/mordor".into(),
                ],
                finality_delay: 3_000u64,
                rate_limit_delay: 5_000u64,
                block_distance: 20u64,
                avg_block_speed: 13_000,
                network_type: ghost_networks::NetworkType::Evm,
                gatekeeper: "0xA59cB4ff90bE2206121aE61eEB68d0AeC7BA095f".into(),
                topic_name: "0x7ab52ec05c331e6257a3d705d6bea6e4c27277351764ad139209e06b203811a6"
                    .into(),
                incoming_fee: 69_000_000u32,
                outgoing_fee: 0u32,
            }
            .encode(),
            5100000000000000000u128,
        ),
    ]
}

/// Helper function to create casper `GenesisConfig` for testing
#[cfg(feature = "casper-native")]
pub fn testnet_config_genesis(
    initial_authorities: Vec<(
        AccountId,
        AccountId,
        BabeId,
        GrandpaId,
        AuthorityDiscoveryId,
        SlowClapId,
    )>,
    endowed_accounts: Option<Vec<AccountId>>,
    ghost_accounts: Option<Vec<(AccountId, u128, u8)>>,
    evm_networks: Option<Vec<(u32, Vec<u8>, u128)>>,
) -> serde_json::Value {
    let endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(casper_testnet_accounts);

    let ghost_accounts: Vec<(AccountId, u128, u8)> = ghost_accounts.unwrap_or_default();

    let evm_networks: Vec<(u32, Vec<u8>, u128)> = evm_networks.unwrap_or_default();

    const ENDOWMENT: u128 = 1_000 * CSPR;
    const STASH: u128 = 500 * CSPR;

    serde_json::json!({
        "balances": {
            "balances": endowed_accounts
                .iter()
                .map(|k| (k.clone(), ENDOWMENT))
                .chain(ghost_accounts
                    .iter()
                    .map(|k| (k.0.clone(), k.1.clone())))
                .collect::<Vec<_>>(),
        },
        "session": {
            "keys": initial_authorities
                .iter()
                .map(|x| {
                    (
                        x.0.clone(),
                        x.0.clone(),
                        casper_session_keys(
                            x.2.clone(),
                            x.3.clone(),
                            x.4.clone(),
                            x.5.clone(),
                        ),
                    )
                })
                .collect::<Vec<_>>(),
        },
        "staking": {
            "validatorCount": initial_authorities.len() as u32,
            "minimumValidatorCount": 1,
            "invulnerables": initial_authorities
                .iter()
                .map(|x| x.0.clone())
                .collect::<Vec<_>>(),
            "forceEra": Forcing::NotForcing,
            "slashRewardFraction": Perbill::from_percent(10),
            "stakers": initial_authorities
                .iter()
                .map(|x| (x.0.clone(), x.0.clone(), STASH, casper::StakerStatus::<AccountId>::Validator))
                .collect::<Vec<_>>(),
        },
        "babe": {
            "epochConfig": Some(casper::BABE_GENESIS_EPOCH_CONFIG),
        },
        "ghostSudo": {
            "key": endowed_accounts
                .first()
                .cloned(),
        },
        "ghostNetworks": {
            "networks": evm_networks,
        },
        "ghostClaims": {
            "total": ghost_accounts.iter().fold(0, |acc, k| acc + k.1),
            "membersAndRanks": ghost_accounts
                .iter()
                .map(|k| (k.0.clone(), k.2.clone()))
                .collect::<Vec<_>>(),
        },
    })
}

#[cfg(feature = "casper-native")]
fn casper_staging_config_genesis() -> serde_json::Value {
    use hex_literal::hex;
    use sp_core::crypto::UncheckedInto;

    // Following keys are used in genesis config for testing (casper) chains.
    // DO NOT use them in production chains such as ghost.
    let endowed_accounts = vec![
        // sfErNwRgZ6ypB7wY8M2smXMZjxqUkc2TgUcNvC1JNQJFXS8bw
        hex!["328d3b7c3046ef7700937d99fb2e98ce2591682c2b5dcf3f562e4da157650237"].into(),
        // sfEwRjyvEQcpRQ1qbCZum27nEkTggKEt7DtqxwyYQULt9UuUN
        hex!["3666e4e19f87bb8680495f31864ce1f1c69d4178002cc01911aef2cc7313f203"].into(),
        // sfFD7KSRi2aSREJWc9X75sVTN4a5pbPM5VSSJefmPvMuiVbPr
        hex!["425ccd7bda4f5c76788ba23bc0381d7a2e496179c93301208c57501c80a4232a"].into(),
        // sfH29zyFYtoFj6fSXskAEXG5XyyMCa5YycahJkYMwSUz8CcEU
        hex!["927a98dcf8f721103005f168476c24b91d7d10d580f457006a908e10e62c7729"].into(),
    ];

    let initial_authorities: Vec<(
        AccountId,
        AccountId,
        BabeId,
        GrandpaId,
        AuthorityDiscoveryId,
        SlowClapId,
    )> = vec![
        (
            // sfFXZmnDVnkQ781J2gbqUpi7K5KgMWMdM4eeii74xxGgKYnNN
            hex!["507045c82be367f95408466cd054ca39bfa52697a3ef22809af14cf9de304f02"].into(),
            // sfFXZmnDVnkQ781J2gbqUpi7K5KgMWMdM4eeii74xxGgKYnNN
            hex!["507045c82be367f95408466cd054ca39bfa52697a3ef22809af14cf9de304f02"].into(),
            // sfJeojACBa7WiH6tBwikBKAMU2oKmseEBD1GYUYATvfWuLcPa
            hex!["daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f"]
                .unchecked_into(),
            // sfFdtzNxJdeEkgHxvk144rJKxf7wcYvgX5tqfgZRutW9YvAKE
            hex!["55446f9a7aa99ced06b317c80ce90d56b84e56526775683af2525969e8da0b64"]
                .unchecked_into(),
            // sfE8gsMYAjAJHk5gyYZN7AW6pfmJ7V9H7xxWto24nmhzCUXaQ
            hex!["12c14850562021eb99f58f90ab624fb6cfaf3ac9228a92f8b60115fe6a6af15a"]
                .unchecked_into(),
            // sfE3GKSrKZzrZpdapJ2VGRpPor45T4D4i8QBZNumSNGqGv7PX
            hex!["0e9e698c7b2bf5ce3861cb4bc4ddf9e200237c282025b093ada850d764d12a35"]
                .unchecked_into(),
        ),
        (
            // sfHLqWNC4hMKHhwvPWmWcxZsDPhCTQKgh1Ap7pm3qML5GBTBa
            hex!["a0ba0196e6ee7e6b5b0553035c5cb5c04e9725001b5732839d0529cbc00c9600"].into(),
            // sfHLqWNC4hMKHhwvPWmWcxZsDPhCTQKgh1Ap7pm3qML5GBTBa
            hex!["a0ba0196e6ee7e6b5b0553035c5cb5c04e9725001b5732839d0529cbc00c9600"].into(),
            // sfGA6tPPF8dAc8QpMCMjxitG3j8sXPhkdpm5bwz4UsXAApUiw
            hex!["6c4dd88b43e2011cf9a6a73d53446336ac9e04cdd4ca23587df63187ac455e49"]
                .unchecked_into(),
            // sfGxQZXFUQH1AXv82rpjiJHFs7YsdEuVGdyvKiS2Tajpvw6Se
            hex!["8f9ea20bf4a807a8e710f7559dece86e94672b5b361de157bdaa5c1f37849f8d"]
                .unchecked_into(),
            // sfGz2enFUR22cQ5ey61MdtPqbCeEWZA1wsCFFSLGaK7vKnv8C
            hex!["90db5ed339a559ed157995a48d781f44c7df972dfba4bc855e4b59fa46438e17"]
                .unchecked_into(),
            // sfEtwe5BoroNjkdLsvnjnMemUKiw8MS1X4YW8bepbbGvhS4LZ
            hex!["3481cdcbcf37a4669c29a78cf9ceb39383a10ef0a18b36b92d149fdd0c24ae00"]
                .unchecked_into(),
        ),
        (
            // sfG9iWUS7AKBzvqdz3uDZv7f1t79vvSTYL1VxzYPHjhmLnQVn
            hex!["6c0283f4c688f0e75ad546c790bbd5961c1a6931543aa589f368f8272c44b758"].into(),
            // sfG9iWUS7AKBzvqdz3uDZv7f1t79vvSTYL1VxzYPHjhmLnQVn
            hex!["6c0283f4c688f0e75ad546c790bbd5961c1a6931543aa589f368f8272c44b758"].into(),
            // sfGMUYXSjHgwGBpbGiHFoqT1DdJwZdHk49H5ViaQM7HUnayvZ
            hex!["74fa7381a7a74b316afb6793a00387eed9d95d46a69866cbb316b5d9c918af0e"]
                .unchecked_into(),
            // sfEWYhczV6PbmeNXZTcA4LvhpzvCfNMatYoSNKvDphmNaQzqB
            hex!["236d2fa03f4ed8cb65de7e514d7540159b328f1c170dd402b094ad7fbf547218"]
                .unchecked_into(),
            // sfDirg32ityDvrjxh4822unhP4qm4S9yTqN99gesN322swn7h
            hex!["00946618c353e4c6546b87f9ca1089b846b0ea4658ee8e6d9d1200c24cb5ee27"]
                .unchecked_into(),
            // sfFZgp1Z5diFAZ16swuQD5GojGCsMMFVR19uWnCrppMLuYjRv
            hex!["520e74f8c5853ec8577932327ad3247656db25b74c79ad09adb431b271002401"]
                .unchecked_into(),
        ),
        (
            // sfHjtrXFzRmxwjE4rjVxFJXpvVv7furjdymZS7PQRFiANpodz
            hex!["b24feb55b2cac4b365a9245c2a97525b01bd1a594d2d42b91f6bc38c9c2e6517"].into(),
            // sfHjtrXFzRmxwjE4rjVxFJXpvVv7furjdymZS7PQRFiANpodz
            hex!["b24feb55b2cac4b365a9245c2a97525b01bd1a594d2d42b91f6bc38c9c2e6517"].into(),
            // sfF5XWwvNPjZEsBz1HWs5Ys5zcE85UHnN1BV8TBBectqFQZRm
            hex!["3c944c704cae203619b9e7a5a4b6742736da6a8e76c762291bebdc7652cfec2f"]
                .unchecked_into(),
            // sfDx3gj4wFHg3cK6bopnypHQ6TxW1VgyPsYVbXKhRHtLDLxQb
            hex!["0aa3a88f6b777c95c3dfe7e997b76798413f16aa325f34824cae0c9102b281d5"]
                .unchecked_into(),
            // sfHXZbnZV3YWwnH28q2xumm7stvuC8LweYYxPNuHGonnX7QHL
            hex!["a8e828d10cf7b74481b6e746e5532d4740ea8014a0d3d856540a59847f8a6b76"]
                .unchecked_into(),
            // sfEaHBrBgeMhRTA3WHE9Nbyvn3h7xkhYKi7go4yo81L88o9zJ
            hex!["2645f1f6820dd3a917eebbdab033088d8862477c1c14759b218685f9a0893377"]
                .unchecked_into(),
        ),
    ];

    let ghost_accounts: Vec<(AccountId, u128, u8)> = casper_testnet_evm_accounts();
    let evm_networks = casper_testnet_evm_networks();

    const ENDOWMENT: u128 = 31 * CSPR;
    const STASH: u128 = 69 * CSPR;

    serde_json::json!({
        "balances": {
            "balances": endowed_accounts
                .iter()
                .map(|k: &AccountId| (k.clone(), ENDOWMENT))
                .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
                .chain(legacy_endowments().iter().map(|x| (x.0.clone(), x.1)))
                .collect::<Vec<_>>(),
        },
        "session": {
            "keys": initial_authorities
                .iter()
                .map(|x| {
                    (
                        x.0.clone(),
                        x.0.clone(),
                        casper_session_keys(
                            x.2.clone(),
                            x.3.clone(),
                            x.4.clone(),
                            x.5.clone(),
                        ),
                    )
                })
                .collect::<Vec<_>>(),
        },
        "staking": {
            "validatorCount": 69,
            "minimumValidatorCount": 3,
            "stakers": initial_authorities
                .iter()
                .map(|x| {
                    (
                        x.0.clone(),
                        x.0.clone(),
                        STASH,
                        casper::StakerStatus::<AccountId>::Validator,
                    )
                })
                .collect::<Vec<_>>(),
            "forceEra": Forcing::NotForcing,
            "slashRewardFraction": Perbill::from_percent(10),
            "minNominatorBond": 690 * STRH,
            "minValidatorBond": 6_900 * STRH,
            "maxNominatorCount": Some(1337),
            "maxValidatorCount": Some(420),
        },
        "babe": {
            "epochConfig": Some(casper::BABE_GENESIS_EPOCH_CONFIG),
        },
        "ghostSudo": {
            "key": endowed_accounts
                .first()
                .cloned(),
        },
        "ghostNetworks": {
            "networks": evm_networks,
        },
        "ghostClaims": {
            "total": ghost_accounts
                .iter()
                .fold(0, |acc, k| acc + k.1),
            "membersAndRanks": ghost_accounts
                .iter()
                .map(|k| (k.0.clone(), k.2.clone()))
                .collect::<Vec<_>>(),
        },
    })
}

#[cfg(feature = "casper-native")]
fn casper_development_config_genesis() -> serde_json::Value {
    testnet_config_genesis(
        vec![get_authority_keys_from_seed("Alice")],
        None,
        None,
        None,
    )
}

#[cfg(feature = "casper-native")]
fn casper_local_config_genesis() -> serde_json::Value {
    testnet_config_genesis(
        vec![
            get_authority_keys_from_seed("Alice"),
            get_authority_keys_from_seed("Bob"),
        ],
        Some(casper_testnet_accounts()),
        Some(casper_testnet_evm_accounts()),
        Some(casper_testnet_evm_networks()),
    )
}

#[cfg(feature = "casper-native")]
pub fn casper_development_config() -> Result<CasperChainSpec, String> {
    Ok(CasperChainSpec::builder(
        casper::WASM_BINARY.ok_or("Casper development wasm not available")?,
        Default::default(),
    )
    .with_name("Development")
    .with_id("casper_dev")
    .with_chain_type(ChainType::Development)
    .with_genesis_config_patch(casper_development_config_genesis())
    .with_protocol_id(DEFAULT_PROTOCOL_ID)
    .with_properties(casper_chain_spec_properties())
    .build())
}

#[cfg(feature = "casper-native")]
pub fn casper_local_testnet_config() -> Result<CasperChainSpec, String> {
    Ok(CasperChainSpec::builder(
        casper::WASM_BINARY.ok_or("Casper local testnet wasm not available")?,
        Default::default(),
    )
    .with_name("Casper Local Testnet")
    .with_id("casper_local_testnet")
    .with_chain_type(ChainType::Local)
    .with_genesis_config_patch(casper_local_config_genesis())
    .with_protocol_id(DEFAULT_PROTOCOL_ID)
    .with_properties(casper_chain_spec_properties())
    .build())
}

#[cfg(feature = "casper-native")]
pub fn casper_staging_testnet_config() -> Result<CasperChainSpec, String> {
    Ok(CasperChainSpec::builder(
        casper::WASM_BINARY.ok_or("Casper staging testnet wasm not available")?,
        Default::default(),
    )
    .with_name("Casper Staging Testnet")
    .with_id("casper_staging_testnet")
    .with_chain_type(ChainType::Live)
    .with_genesis_config_patch(casper_staging_config_genesis())
    .with_telemetry_endpoints(
        TelemetryEndpoints::new(vec![(CASPER_TELEMETRY_URL.to_string(), 0)])
            .expect("Casper Staging telemetry url is valid; qed"),
    )
    .with_protocol_id(DEFAULT_PROTOCOL_ID)
    .with_properties(casper_chain_spec_properties())
    .build())
}
