use std::{
    collections::HashMap, path::Path, str::FromStr, sync::Arc, time::Duration,
};
use parking_lot::Mutex;
use rand_08::{prelude::StdRng, SeedableRng};
use threadpool::ThreadPool;
use cfx_internal_common::ChainIdParamsInner;
use cfx_parameters::{
    block::{MAX_BLOCK_SIZE_IN_BYTES, REFEREE_DEFAULT_BOUND},
    consensus::{GENESIS_GAS_LIMIT, TRANSACTION_DEFAULT_EPOCH_BOUND},
    tx_pool::TXPOOL_DEFAULT_NONCE_BITS,
    WORKER_COMPUTATION_PARALLELISM,
};
use cfx_storage::{StorageConfiguration, StorageManager};
use cfx_types::{
    address_util::AddressUtil, Address, AddressSpaceUtil, AllChainID, H256,
    U256,
};
use diem_config::keys::ConfigKey;
use diem_crypto::Uniform;
use diem_types::validator_config::{
    ConsensusPrivateKey, ConsensusVRFPrivateKey,
};
use primitives::{Block, BlockHeaderBuilder};
use crate::{
    block_data_manager::{BlockDataManager, DataManagerConfiguration, DbType},
    cache_config::CacheConfig,
    consensus::{
        consensus_inner::consensus_executor::ConsensusExecutionConfiguration,
        pos_handler::{PosConfiguration, PosVerifier},
        ConsensusConfig, ConsensusInnerConfig,
    },
    db::NUM_COLUMNS,
    genesis_block::{genesis_block, GenesisPosState},
    pow::{self, PowComputer, ProofOfWorkConfig},
    statistics::Statistics,
    sync::{SyncGraphConfig, SynchronizationGraph},
    transaction_pool::TxPoolConfig,
    verification::VerificationConfig,
    ConsensusGraph, NodeType, Notifications, TransactionPool,
};
use cfx_executor::{
    machine::{Machine, VmFactory},
    spec::CommonParams,
};
pub fn create_simple_block_impl(
    parent_hash: H256, ref_hashes: Vec<H256>, height: u64, nonce: U256,
    diff: U256, block_weight: u32, adaptive: bool,
) -> (H256, Block) {
    let mut b = BlockHeaderBuilder::new();
    let mut author = Address::zero();
    author.set_user_account_type_bits();
    let mut header = b
        .with_parent_hash(parent_hash)
        .with_height(height)
        .with_referee_hashes(ref_hashes)
        .with_gas_limit(GENESIS_GAS_LIMIT.into())
        .with_nonce(nonce)
        .with_difficulty(diff)
        .with_adaptive(adaptive)
        .with_author(author)
        .build();
    header.compute_hash();
    let pow_quality = if block_weight > 1 {
        diff * block_weight
    } else {
        diff
    };
    header.pow_hash =
        Some(pow::pow_quality_to_hash(&pow_quality, &header.nonce()));
    let block = Block::new(header, vec![]);
    (block.hash(), block)
}
pub fn create_simple_block(
    sync: Arc<SynchronizationGraph>, parent_hash: H256, ref_hashes: Vec<H256>,
    height: u64, block_weight: u32, adaptive: bool,
) -> (H256, Block) {
    let exp_diff = U256::from(10);
    let nonce = U256::from(sync.block_count() as u64 + 1);
    create_simple_block_impl(
        parent_hash,
        ref_hashes,
        height,
        nonce,
        exp_diff,
        block_weight,
        adaptive,
    )
}
pub fn initialize_data_manager(
    db_dir: &str, dbtype: DbType, pow: Arc<PowComputer>, vm: VmFactory,
) -> (Arc<BlockDataManager>, Arc<Block>) {
    let ledger_db = db::open_database(
        db_dir,
        &db::db_config(
            Path::new(db_dir),
            Some(128),
            db::DatabaseCompactionProfile::default(),
            NUM_COLUMNS,
            false,
        ),
    )
    .map_err(|e| format!("Failed to open database {:?}", e))
    .unwrap();
    let worker_thread_pool = Arc::new(Mutex::new(ThreadPool::with_name(
        "Tx Recover".into(),
        WORKER_COMPUTATION_PARALLELISM,
    )));
    let storage_manager = Arc::new(
        StorageManager::new(StorageConfiguration::new_default(
            db_dir,
            cfx_parameters::consensus::SNAPSHOT_EPOCHS_CAPACITY,
            cfx_parameters::consensus::ERA_DEFAULT_EPOCH_COUNT,
        ))
        .expect("Failed to initialize storage."),
    );
    let mut genesis_accounts = HashMap::new();
    genesis_accounts.insert(
        Address::from_str("1000000000000000000000000000000000000008")
            .unwrap()
            .with_native_space(),
        U256::from(0),
    );
    let machine = Arc::new(Machine::new_with_builtin(Default::default(), vm));
    let genesis_block = Arc::new(genesis_block(
        &storage_manager,
        genesis_accounts,
        Address::from_str("1000000000000000000000000000000000000008").unwrap(),
        U256::from(10),
        machine.clone(),
        false, None,
        &Some(GenesisPosState {
            initial_nodes: vec![],
            initial_committee: vec![],
            initial_seed: Default::default(),
        }),
    ));
    let data_man = Arc::new(BlockDataManager::new(
        CacheConfig::default(),
        genesis_block.clone(),
        ledger_db.clone(),
        storage_manager,
        worker_thread_pool,
        DataManagerConfiguration::new(
            false,                          false, Duration::from_millis(300_000), dbtype,
        ),
        pow,
    ));
    (data_man, genesis_block)
}
pub fn initialize_synchronization_graph_with_data_manager(
    data_man: Arc<BlockDataManager>, beta: u64, h: u64, tcr: u64, tcb: u64,
    era_epoch_count: u64, pow: Arc<PowComputer>, vm: VmFactory,
) -> (Arc<SynchronizationGraph>, Arc<ConsensusGraph>) {
    let mut params = CommonParams::default();
    params.transition_heights.cip1559 = u64::MAX;
    let machine = Arc::new(Machine::new_with_builtin(params.clone(), vm));
    let mut rng = StdRng::from_seed([0u8; 32]);
    let pos_verifier = Arc::new(PosVerifier::new(
        None,
        PosConfiguration {
            bls_key: ConfigKey::new(ConsensusPrivateKey::generate(&mut rng)),
            vrf_key: ConfigKey::new(ConsensusVRFPrivateKey::generate(&mut rng)),
            diem_conf_path: Default::default(),
            protocol_conf: Default::default(),
            pos_initial_nodes_path: "".to_string(),
            vrf_proposal_threshold: Default::default(),
            pos_state_config: Default::default(),
        },
        u64::MAX,
    ));
    let verification_config = VerificationConfig::new(
        true, REFEREE_DEFAULT_BOUND,
        MAX_BLOCK_SIZE_IN_BYTES,
        TRANSACTION_DEFAULT_EPOCH_BOUND,
        TXPOOL_DEFAULT_NONCE_BITS,
        pos_verifier.enable_height(),
        machine.clone(),
    );
    let txpool = Arc::new(TransactionPool::new(
        TxPoolConfig::default(),
        verification_config.clone(),
        data_man.clone(),
        machine.clone(),
    ));
    let statistics = Arc::new(Statistics::new());
    let pow_config = ProofOfWorkConfig::new(
        true,      false,     "disable", Some(10),
        String::from(""), 0,                None,             1,                0,                );
    let sync_config = SyncGraphConfig {
        future_block_buffer_capacity: 1,
        enable_state_expose: false,
        is_consortium: false,
    };
    let notifications = Notifications::init();
    let consensus = Arc::new(ConsensusGraph::new(
        ConsensusConfig {
            chain_id: ChainIdParamsInner::new_simple(AllChainID::new(1, 1)),
            inner_conf: ConsensusInnerConfig {
                adaptive_weight_beta: beta,
                heavy_block_difficulty_ratio: h,
                timer_chain_block_difficulty_ratio: tcr,
                timer_chain_beta: tcb,
                era_epoch_count,
                enable_optimistic_execution: false,
                enable_state_expose: false,
                pos_pivot_decision_defer_epoch_count: 50,
                cip113_pivot_decision_defer_epoch_count: 50,
                cip113_transition_height: u64::MAX,
                debug_dump_dir_invalid_state_root: None,
                debug_invalid_state_root_epoch: None,
                force_recompute_height_during_construct_pivot: None,
                recovery_latest_mpt_snapshot: false,
                use_isolated_db_for_mpt_table: false,
            },
            bench_mode: true, transaction_epoch_bound: TRANSACTION_DEFAULT_EPOCH_BOUND,
            referee_bound: REFEREE_DEFAULT_BOUND,
            get_logs_epoch_batch_size: 32,
            get_logs_filter_max_epoch_range: None,
            get_logs_filter_max_block_number_range: None,
            get_logs_filter_max_limit: None,
            sync_state_starting_epoch: None,
            sync_state_epoch_gap: None,
            pivot_hint_conf: None,
        },
        txpool.clone(),
        statistics.clone(),
        data_man.clone(),
        pow_config.clone(),
        pow.clone(),
        notifications.clone(),
        ConsensusExecutionConfiguration {
            executive_trace: false,
        },
        verification_config.clone(),
        NodeType::Archive,
        pos_verifier.clone(),
        None,
        params,
    ));
    let sync = Arc::new(SynchronizationGraph::new(
        consensus.clone(),
        data_man.clone(),
        statistics.clone(),
        verification_config,
        pow_config,
        pow.clone(),
        sync_config,
        notifications,
        machine,
        pos_verifier.clone(),
    ));
    (sync, consensus)
}
pub fn initialize_synchronization_graph(
    db_dir: &str, beta: u64, h: u64, tcr: u64, tcb: u64, era_epoch_count: u64,
    dbtype: DbType,
) -> (
    Arc<SynchronizationGraph>,
    Arc<ConsensusGraph>,
    Arc<BlockDataManager>,
    Arc<Block>,
) {
    let vm = VmFactory::new(1024 * 32);
    let pow = Arc::new(PowComputer::new(true));
    let (data_man, genesis_block) =
        initialize_data_manager(db_dir, dbtype, pow.clone(), vm.clone());
    let (sync, consensus) = initialize_synchronization_graph_with_data_manager(
        data_man.clone(),
        beta,
        h,
        tcr,
        tcb,
        era_epoch_count,
        pow,
        vm,
    );
    (sync, consensus, data_man, genesis_block)
}