use pallet_staking::Forcing;
use sp_staking::StakerStatus;

use casper_runtime_constants::currency::CSPR;
use primitives::{AccountId, AccountPublic};

use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId;
use babe_primitives::AuthorityId as BabeId;
use grandpa_primitives::AuthorityId as GrandpaId;

use hex_literal::hex;
use sp_core::crypto::UncheckedFrom;

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;

use crate::{Balance, NetworkId};

use ghost_weaver::sr25519::AuthorityId as WeaverId;
use ghost_exodus::sr25519::AuthorityId as ExodusId;
use ghost_helpers::networks::{
    NetworkClaim, NetworkInitiation, NetworkInitiationBuilder,
    NetworkDataBuilder, NetworkType, NetworkCurve,
};

#[derive(Clone)]
pub struct AuthorityInitiation {
    stash: AccountId,
    account: AccountId,
    babe_id: BabeId,
    grandpa_id: GrandpaId,
    authority_discovery_id: AuthorityDiscoveryId,
    weaver_id: WeaverId,
    exodus_id: ExodusId,
}

impl AuthorityInitiation {
    pub fn casper_session_keys(self) -> crate::opaque::SessionKeys {
        crate::opaque::SessionKeys {
            babe: self.babe_id,
            grandpa: self.grandpa_id,
            authority_discovery: self.authority_discovery_id,
            weaver: self.weaver_id,
            exodus: self.exodus_id,
        }
    }
}

#[derive(Default)]
struct AuthorityInitiationBuilder {
    stash: Option<AccountId>,
    account: Option<AccountId>,
    babe_id: Option<BabeId>,
    grandpa_id: Option<GrandpaId>,
    authority_discovery_id: Option<AuthorityDiscoveryId>,
    weaver_id: Option<WeaverId>,
    exodus_id: Option<ExodusId>,
}

impl AuthorityInitiationBuilder {
    pub fn with_stash(mut self, stash: AccountId) -> Self {
        self.stash = Some(stash);
        self
    }

    pub fn with_account(mut self, account: AccountId) -> Self {
        self.account = Some(account);
        self
    }

    pub fn with_babe_id(mut self, babe_id: BabeId) -> Self {
        self.babe_id = Some(babe_id);
        self
    }

    pub fn with_grandpa_id(mut self, grandpa_id: GrandpaId) -> Self {
        self.grandpa_id = Some(grandpa_id);
        self
    }

    pub fn with_authority_id(mut self, authority_id: AuthorityDiscoveryId,) -> Self {
        self.authority_discovery_id = Some(authority_id);
        self
    }

    pub fn with_weaver_id(mut self, weaver_id: WeaverId) -> Self {
        self.weaver_id = Some(weaver_id);
        self
    }

    pub fn with_exodus_id(mut self, exodus_id: ExodusId) -> Self {
        self.exodus_id = Some(exodus_id);
        self
    }

    pub fn build(self) -> AuthorityInitiation {
        AuthorityInitiation {
            stash: self.stash.expect("No stash key provided during authority initiation."),
            account: self.account.expect("No account key provided during authority initiation."),
            babe_id: self.babe_id.expect("No babe key provided during authority initiation."),
            grandpa_id: self.grandpa_id.expect("No gran key provided during authority initiation."),
            authority_discovery_id: self.authority_discovery_id.expect("No audi key provided during authority initiation."),
            weaver_id: self.weaver_id.expect("No weav key provided during authority initiation."),
            exodus_id: self.exodus_id.expect("No exds key provided during authority initiation."),
        }
    }
}

pub struct AccountInitiation {
    account: AccountId,
    balance: u128,
}

#[derive(Default)]
struct AccountInitiationBuilder {
    account: Option<AccountId>,
    balance: u128,
}

impl AccountInitiationBuilder {
    pub fn with_account(mut self, account: AccountId) -> Self {
        self.account = Some(account);
        self
    }

    pub fn with_balance(mut self, balance: u128) -> Self {
        self.balance = balance;
        self
    }

    pub fn build(self) -> AccountInitiation {
        AccountInitiation {
            balance: self.balance,
            account: self.account
                .expect("No account key provided during account initiation."),
        }
    }

}

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,
) -> AuthorityInitiation {
    let stash = get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed));
    let account = get_account_id_from_seed::<sr25519::Public>(seed);

    let babe_id = get_from_seed::<BabeId>(seed);
    let weaver_id = get_from_seed::<WeaverId>(seed);
    let exodus_id = get_from_seed::<ExodusId>(seed);
    let grandpa_id = get_from_seed::<GrandpaId>(seed);
    let authority_id = get_from_seed::<AuthorityDiscoveryId>(seed);

    AuthorityInitiationBuilder::default()
        .with_stash(stash)
        .with_account(account)
        .with_babe_id(babe_id)
        .with_weaver_id(weaver_id)
        .with_exodus_id(exodus_id)
        .with_grandpa_id(grandpa_id)
        .with_authority_id(authority_id)
        .build()
}

fn casper_staging_initial_authorities() -> Vec<AuthorityInitiation> {
    vec![
        // sfFXZmnDVnkQ781J2gbqUpi7K5KgMWMdM4eeii74xxGgKYnNN
        AuthorityInitiationBuilder::default()
    		.with_stash(AccountId::new(hex!("507045c82be367f95408466cd054ca39bfa52697a3ef22809af14cf9de304f02")))
    		.with_account(AccountId::new(hex!("507045c82be367f95408466cd054ca39bfa52697a3ef22809af14cf9de304f02")))
    		.with_babe_id(BabeId::unchecked_from(hex!("daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f")))
    		.with_grandpa_id(GrandpaId::unchecked_from(hex!("55446f9a7aa99ced06b317c80ce90d56b84e56526775683af2525969e8da0b64")))
    		.with_authority_id(AuthorityDiscoveryId::unchecked_from(hex!("12c14850562021eb99f58f90ab624fb6cfaf3ac9228a92f8b60115fe6a6af15a")))
        	.with_weaver_id(WeaverId::unchecked_from(hex!("0e9e698c7b2bf5ce3861cb4bc4ddf9e200237c282025b093ada850d764d12a35")))
        	.with_exodus_id(ExodusId::unchecked_from(hex!("0e9e698c7b2bf5ce3861cb4bc4ddf9e200237c282025b093ada850d764d12a35")))
        	.build(),

        // TODO: revisit
    ]
}

fn casper_testnet_networks() -> Vec<NetworkInitiation<NetworkId, Balance>> {
    vec![
        NetworkInitiationBuilder::default()
        	.with_network_id(11155111)
        	.with_gatekeeper_amount(0)
        	.with_network_data(
        	    NetworkDataBuilder::default()
        	    	.with_gatekeeper(hex!("9cFfBdBdF29C67c5DbAB1B5E8Ae897AeAFcaf70C").to_vec())
        	    	.with_selector(hex!("d4be7935").to_vec())
        	    	.with_default_endpoints(
        	    		vec![
        	    	        b"https://sepolia.drpc.org".to_vec(),
        	    	        b"https://sepolia.gateway.tenderly.co".to_vec(),
        	    	        b"https://api.zan.top/eth-sepolia".to_vec(),
        	    	        b"https://rpc.sepolia.ethpandaops.io".to_vec(),
        	    	        b"https://ethereum-sepolia-rpc.publicnode.com".to_vec(),
        	    	        b"https://1rpc.io/sepolia".to_vec(),
        	    	        b"https://0xrpc.io/sep".to_vec(),
        	    	        b"https://eth-sepolia.api.onfinality.io/public".to_vec(),
        	    		]
        	    	)
        	    	.with_network_type(NetworkType::Evm)
        	    	.with_network_curve(NetworkCurve::Secp256k1)
        	    	.with_finality_delay(69)
        	    	.with_rate_limit_delay(5_000)
        	    	.with_block_deviation(420)
        	    	.with_incoming_fee(69_000_000u32)
        	    	.with_outgoing_fee(69_000_000u32)
            		.build()
            )
        	.build()
    ]
}

pub fn testnet_config_genesis(
    initial_authorities: Vec<AuthorityInitiation>,
    initial_accounts: Vec<AccountInitiation>,
    initial_networks: Vec<NetworkInitiation<NetworkId, Balance>>,
    initial_claims: Vec<NetworkClaim<NetworkId, Balance>>,
) -> serde_json::Value {
    const ENDOWMENT: u128 = 31 * CSPR;
    const STASH: u128 = 69 * CSPR;

    let sudo = initial_authorities.first().map(|x| x.account.clone()).unwrap();

    serde_json::json!({
        "balances": {
            "balances": initial_accounts
            	.iter()
            	.map(|x| (x.account.clone(), x.balance))
            	.chain(initial_authorities.iter().map(|x| (x.account.clone(), ENDOWMENT)))
            	.chain(initial_authorities.iter().map(|x| (x.stash.clone(), STASH)))
            	.collect::<Vec<_>>(),
        },
        "session": {
            "keys": initial_authorities
            	.iter()
            	.map(|x| {
            	    (x.stash.clone(), x.stash.clone(), x.clone().casper_session_keys())
            	})
            	.collect::<Vec<_>>(),
        },
        "staking": {
            "validatorCount": initial_authorities.len() as u32,
            "minimumValidatorCount": 4,
            "invulnerables": initial_authorities
            	.iter()
            	.map(|x| x.stash.clone())
            	.collect::<Vec<_>>(),
            "forceEra": Forcing::NotForcing,
            "slashRewardFraction": Perbill::from_percent(10),
            "stakers": initial_authorities
            	.iter()
            	.map(|x| (
            	    x.stash.clone(),
            	    x.stash.clone(),
            	    STASH,
            	    StakerStatus::<AccountId>::Validator,
            	))
            	.collect::<Vec<_>>(),
        },
        "babe": { "epochConfig": Some(crate::BABE_GENESIS_EPOCH_CONFIG) },
        "ghostSudo": { "key": sudo },
        "ghostNetworks": { "networks": initial_networks },
        "ghostGovernance": { "networkClaims": initial_claims },
        "ghostWeaver": { "authorities": [], "loomStates": [] },
        "ghostExodus": { "authorities": [] }
    })
}

pub fn casper_development_config_genesis() -> serde_json::Value {
    let initial_authorities = vec![get_authority_keys_from_seed("Alice")];
    testnet_config_genesis(
        initial_authorities,
        Default::default(),
        Default::default(),
        Default::default(),
    )
}

pub fn casper_local_config_genesis() -> serde_json::Value {
    let initial_authorities = vec![
        get_authority_keys_from_seed("Alice"),
        get_authority_keys_from_seed("Bob"),
        get_authority_keys_from_seed("Charlie"),
        get_authority_keys_from_seed("Dave"),
    ];

    let initial_accounts = vec![
        AccountInitiationBuilder::default()
        	.with_account(get_account_id_from_seed::<sr25519::Public>("Eve"))
        	.with_balance(420 * CSPR)
        	.build(),
        AccountInitiationBuilder::default()
        	.with_account(get_account_id_from_seed::<sr25519::Public>("Ferdie"))
        	.with_balance(1337 * CSPR)
        	.build(),
    ];

    testnet_config_genesis(
        initial_authorities,
        initial_accounts,
        casper_testnet_networks(),
        Default::default(),
    )
}

pub fn casper_staging_config_genesis() -> serde_json::Value {
    testnet_config_genesis(
        casper_staging_initial_authorities(),
        Default::default(),
        casper_testnet_networks(),
        Default::default(),
    )
}

/// 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("casper_development") => casper_development_config_genesis(),
        Ok("casper_local_testnest") => casper_local_config_genesis(),
        Ok("casper_staging_testnet") => casper_staging_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("casper_staging_testnet"),
        sp_genesis_builder::PresetId::from("casper_local_testnet"),
        sp_genesis_builder::PresetId::from("casper_development"),
    ])
}
