use codec::Encode;
use pallet_staking::Forcing;
use sp_staking::StakerStatus;

use crate::{opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG};
use casper_runtime_constants::currency::CSPR;
use primitives::{AccountId, AccountPublic};

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;

use sp_core::{sr25519, Pair, Public};
use sp_runtime::{traits::IdentifyAccount, Perbill};
#[cfg(not(feature = "std"))]
use sp_std::alloc::format;
use sp_std::prelude::*;
use sp_std::vec::Vec;

#[derive(Encode, Clone)]
struct PreparedNetworkData {
    chain_name: Vec<u8>,
    default_endpoint: Vec<u8>,
    finality_delay: Option<u64>,
    release_delay: Option<u64>,
    network_type: u8,
    gatekeeper: Vec<u8>,
    topic_name: Vec<u8>,
    incoming_fee: u32,
    outgoing_fee: u32,
}

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()
}

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()
}

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),
    )
}

fn casper_testnet_accounts() -> Vec<AccountId> {
    Vec::from([
        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"),
    ])
}

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,
        ),
    ]
}

fn casper_testnet_evm_networks() -> Vec<(u32, Vec<u8>)> {
    vec![
        (
            1,
            PreparedNetworkData {
                chain_name: "ethereum-mainnet".into(),
                default_endpoint:
                    "https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/".into(),
                finality_delay: Some(40),
                release_delay: Some(80),
                network_type: Default::default(),
                gatekeeper: "0x4d224452801aced8b2f0aebe155379bb5d594381".into(),
                topic_name: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
                    .into(),
                incoming_fee: 0,
                outgoing_fee: 0,
            }
            .encode(),
        ),
        (
            56,
            PreparedNetworkData {
                chain_name: "bnb-mainnet".into(),
                default_endpoint:
                    "https://bsc-mainnet.core.chainstack.com/35848e183f3e3303c8cfeacbea831cab/"
                        .into(),
                finality_delay: Some(20),
                release_delay: Some(40),
                network_type: Default::default(),
                gatekeeper: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82".into(),
                topic_name: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
                    .into(),
                incoming_fee: 0,
                outgoing_fee: 0,
            }
            .encode(),
        ),
    ]
}

fn casper_session_keys(
    babe: BabeId,
    grandpa: GrandpaId,
    authority_discovery: AuthorityDiscoveryId,
    slow_clap: SlowClapId,
) -> SessionKeys {
    SessionKeys {
        babe,
        grandpa,
        authority_discovery,
        slow_clap,
    }
}

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>)>>,
) -> 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_else(casper_testnet_evm_accounts);

    let evm_networks: Vec<(u32, Vec<u8>)> =
        evm_networks.unwrap_or_else(casper_testnet_evm_networks);

    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, StakerStatus::<AccountId>::Validator))
                .collect::<Vec<_>>(),
        },
        "babe": {
            "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG),
        },
        "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<_>>(),
        },
    })
}

// development
fn casper_development_config_genesis() -> serde_json::Value {
    testnet_config_genesis(
        vec![get_authority_keys_from_seed("Alice")],
        None,
        None,
        None,
    )
}

// local
fn casper_local_config_genesis() -> serde_json::Value {
    testnet_config_genesis(
        vec![
            get_authority_keys_from_seed("Alice"),
            get_authority_keys_from_seed("Bob"),
        ],
        None,
        None,
        None,
    )
}

/// Provides the JSON representation of predefined genesis config for given `id`.
pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option<sp_std::vec::Vec<u8>> {
    let patch = match id.try_into() {
        Ok("development") => casper_development_config_genesis(),
        Ok("local_testnet") => casper_local_config_genesis(),
        _ => return None,
    };
    Some(
        serde_json::to_string(&patch)
            .expect("serialization to json is expected to work; qed")
            .into_bytes(),
    )
}

/// Returns a list of identifiers for available builtin `RuntimeGenesisConfig` presets.
pub fn preset_names() -> Vec<sp_genesis_builder::PresetId> {
    Vec::from([
        sp_genesis_builder::PresetId::from("local_testnet"),
        sp_genesis_builder::PresetId::from("development"),
    ])
}
