use {
    super::{
        storage::SerializableAccountStorageEntry,
        utils::{serialize_iter_as_map, serialize_iter_as_seq},
        *,
    },
    crate::{
        bank::EpochRewardStatus,
        stakes::{serde_stakes_enum_compat, StakesEnum},
    },
    solana_accounts_db::{accounts_hash::AccountsHash, ancestors::AncestorsForSerialization},
    solana_measure::measure::Measure,
    solana_sdk::{deserialize_utils::ignore_eof_error, stake::state::Delegation},
    std::{cell::RefCell, collections::HashSet, sync::RwLock},
};

pub(super) type AccountsDbFields = super::AccountsDbFields<SerializableAccountStorageEntry>;

#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, AbiExample)]
struct UnusedAccounts {
    unused1: HashSet<Pubkey>,
    unused2: HashSet<Pubkey>,
    unused3: HashMap<Pubkey, u64>,
}

// Deserializable version of Bank which need not be serializable,
// because it's handled by SerializableVersionedBank.
// So, sync fields with it!
#[derive(Clone, Deserialize)]
struct DeserializableVersionedBank {
    blockhash_queue: BlockhashQueue,
    ancestors: AncestorsForSerialization,
    hash: Hash,
    parent_hash: Hash,
    parent_slot: Slot,
    hard_forks: HardForks,
    transaction_count: u64,
    tick_height: u64,
    signature_count: u64,
    capitalization: u64,
    max_tick_height: u64,
    hashes_per_tick: Option<u64>,
    ticks_per_slot: u64,
    ns_per_slot: u128,
    genesis_creation_time: UnixTimestamp,
    slots_per_year: f64,
    accounts_data_len: u64,
    slot: Slot,
    epoch: Epoch,
    block_height: u64,
    collector_id: Pubkey,
    collector_fees: u64,
    fee_calculator: FeeCalculator,
    fee_rate_governor: FeeRateGovernor,
    collected_rent: u64,
    rent_collector: RentCollector,
    epoch_schedule: EpochSchedule,
    inflation: Inflation,
    stakes: Stakes<Delegation>,
    #[allow(dead_code)]
    unused_accounts: UnusedAccounts,
    epoch_stakes: HashMap<Epoch, EpochStakes>,
    is_delta: bool,
}

impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
    fn from(dvb: DeserializableVersionedBank) -> Self {
        BankFieldsToDeserialize {
            blockhash_queue: dvb.blockhash_queue,
            ancestors: dvb.ancestors,
            hash: dvb.hash,
            parent_hash: dvb.parent_hash,
            parent_slot: dvb.parent_slot,
            hard_forks: dvb.hard_forks,
            transaction_count: dvb.transaction_count,
            tick_height: dvb.tick_height,
            signature_count: dvb.signature_count,
            capitalization: dvb.capitalization,
            max_tick_height: dvb.max_tick_height,
            hashes_per_tick: dvb.hashes_per_tick,
            ticks_per_slot: dvb.ticks_per_slot,
            ns_per_slot: dvb.ns_per_slot,
            genesis_creation_time: dvb.genesis_creation_time,
            slots_per_year: dvb.slots_per_year,
            accounts_data_len: dvb.accounts_data_len,
            slot: dvb.slot,
            epoch: dvb.epoch,
            block_height: dvb.block_height,
            collector_id: dvb.collector_id,
            collector_fees: dvb.collector_fees,
            fee_calculator: dvb.fee_calculator,
            fee_rate_governor: dvb.fee_rate_governor,
            collected_rent: dvb.collected_rent,
            rent_collector: dvb.rent_collector,
            epoch_schedule: dvb.epoch_schedule,
            inflation: dvb.inflation,
            stakes: dvb.stakes,
            epoch_stakes: dvb.epoch_stakes,
            is_delta: dvb.is_delta,
            incremental_snapshot_persistence: None,
            epoch_accounts_hash: None,
            epoch_reward_status: EpochRewardStatus::Inactive,
        }
    }
}

// Serializable version of Bank, not Deserializable to avoid cloning by using refs.
// Sync fields with DeserializableVersionedBank!
#[derive(Serialize)]
struct SerializableVersionedBank<'a> {
    blockhash_queue: &'a RwLock<BlockhashQueue>,
    ancestors: &'a AncestorsForSerialization,
    hash: Hash,
    parent_hash: Hash,
    parent_slot: Slot,
    hard_forks: &'a RwLock<HardForks>,
    transaction_count: u64,
    tick_height: u64,
    signature_count: u64,
    capitalization: u64,
    max_tick_height: u64,
    hashes_per_tick: Option<u64>,
    ticks_per_slot: u64,
    ns_per_slot: u128,
    genesis_creation_time: UnixTimestamp,
    slots_per_year: f64,
    accounts_data_len: u64,
    slot: Slot,
    epoch: Epoch,
    block_height: u64,
    collector_id: Pubkey,
    collector_fees: u64,
    fee_calculator: FeeCalculator,
    fee_rate_governor: FeeRateGovernor,
    collected_rent: u64,
    rent_collector: RentCollector,
    epoch_schedule: EpochSchedule,
    inflation: Inflation,
    #[serde(serialize_with = "serde_stakes_enum_compat::serialize")]
    stakes: StakesEnum,
    unused_accounts: UnusedAccounts,
    epoch_stakes: &'a HashMap<Epoch, EpochStakes>,
    is_delta: bool,
}

impl<'a> From<crate::bank::BankFieldsToSerialize<'a>> for SerializableVersionedBank<'a> {
    fn from(rhs: crate::bank::BankFieldsToSerialize<'a>) -> Self {
        Self {
            blockhash_queue: rhs.blockhash_queue,
            ancestors: rhs.ancestors,
            hash: rhs.hash,
            parent_hash: rhs.parent_hash,
            parent_slot: rhs.parent_slot,
            hard_forks: rhs.hard_forks,
            transaction_count: rhs.transaction_count,
            tick_height: rhs.tick_height,
            signature_count: rhs.signature_count,
            capitalization: rhs.capitalization,
            max_tick_height: rhs.max_tick_height,
            hashes_per_tick: rhs.hashes_per_tick,
            ticks_per_slot: rhs.ticks_per_slot,
            ns_per_slot: rhs.ns_per_slot,
            genesis_creation_time: rhs.genesis_creation_time,
            slots_per_year: rhs.slots_per_year,
            accounts_data_len: rhs.accounts_data_len,
            slot: rhs.slot,
            epoch: rhs.epoch,
            block_height: rhs.block_height,
            collector_id: rhs.collector_id,
            collector_fees: rhs.collector_fees,
            fee_calculator: rhs.fee_calculator,
            fee_rate_governor: rhs.fee_rate_governor,
            collected_rent: rhs.collected_rent,
            rent_collector: rhs.rent_collector,
            epoch_schedule: rhs.epoch_schedule,
            inflation: rhs.inflation,
            stakes: StakesEnum::from(rhs.stakes.stakes().clone()),
            unused_accounts: UnusedAccounts::default(),
            epoch_stakes: rhs.epoch_stakes,
            is_delta: rhs.is_delta,
        }
    }
}

#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl<'a> solana_frozen_abi::abi_example::IgnoreAsHelper for SerializableVersionedBank<'a> {}

#[derive(PartialEq, Eq)]
pub(super) struct Context {}

impl<'a> TypeContext<'a> for Context {
    type SerializableAccountStorageEntry = SerializableAccountStorageEntry;

    fn serialize_bank_and_storage<S: serde::ser::Serializer>(
        serializer: S,
        serializable_bank: &SerializableBankAndStorage<'a, Self>,
    ) -> std::result::Result<S::Ok, S::Error>
    where
        Self: std::marker::Sized,
    {
        let ancestors = HashMap::from(&serializable_bank.bank.ancestors);
        let fields = serializable_bank.bank.get_fields_to_serialize(&ancestors);
        let lamports_per_signature = fields.fee_rate_governor.lamports_per_signature;
        let epoch_reward_status = serializable_bank
            .bank
            .get_epoch_reward_status_to_serialize();
        match get_serialize_bank_fields(
            SerializableVersionedBank::from(fields),
            SerializableAccountsDb::<'a, Self> {
                accounts_db: &serializable_bank.bank.rc.accounts.accounts_db,
                slot: serializable_bank.bank.rc.slot,
                account_storage_entries: serializable_bank.snapshot_storages,
                phantom: std::marker::PhantomData,
            },
            // Additional fields, we manually store the lamps per signature here so that
            // we can grab it on restart.
            // TODO: if we do a snapshot version bump, consider moving this out.
            lamports_per_signature,
            None::<BankIncrementalSnapshotPersistence>,
            serializable_bank
                .bank
                .get_epoch_accounts_hash_to_serialize()
                .map(|epoch_accounts_hash| *epoch_accounts_hash.as_ref()),
            epoch_reward_status,
        ) {
            BankFieldsToSerialize::WithoutEpochRewardStatus(data) => data.serialize(serializer),
            BankFieldsToSerialize::WithEpochRewardStatus(data) => data.serialize(serializer),
        }
    }

    #[cfg(test)]
    fn serialize_bank_and_storage_without_extra_fields<S: serde::ser::Serializer>(
        serializer: S,
        serializable_bank: &SerializableBankAndStorageNoExtra<'a, Self>,
    ) -> std::result::Result<S::Ok, S::Error>
    where
        Self: std::marker::Sized,
    {
        let ancestors = HashMap::from(&serializable_bank.bank.ancestors);
        let fields = serializable_bank.bank.get_fields_to_serialize(&ancestors);
        (
            SerializableVersionedBank::from(fields),
            SerializableAccountsDb::<'a, Self> {
                accounts_db: &serializable_bank.bank.rc.accounts.accounts_db,
                slot: serializable_bank.bank.rc.slot,
                account_storage_entries: serializable_bank.snapshot_storages,
                phantom: std::marker::PhantomData,
            },
        )
            .serialize(serializer)
    }

    fn serialize_accounts_db_fields<S: serde::ser::Serializer>(
        serializer: S,
        serializable_db: &SerializableAccountsDb<'a, Self>,
    ) -> std::result::Result<S::Ok, S::Error>
    where
        Self: std::marker::Sized,
    {
        // sample write version before serializing storage entries
        let version = serializable_db
            .accounts_db
            .write_version
            .load(Ordering::Acquire);

        // (1st of 3 elements) write the list of account storage entry lists out as a map
        let entry_count = RefCell::<usize>::new(0);
        let entries =
            serialize_iter_as_map(serializable_db.account_storage_entries.iter().map(|x| {
                *entry_count.borrow_mut() += x.len();
                (
                    x.first().unwrap().slot(),
                    serialize_iter_as_seq(
                        x.iter()
                            .map(|x| Self::SerializableAccountStorageEntry::from(x.as_ref())),
                    ),
                )
            }));
        let slot = serializable_db.slot;
        let accounts_delta_hash = serializable_db
            .accounts_db
            .get_accounts_delta_hash(slot)
            .map(Into::into)
            .unwrap_or_else(|| panic!("Missing accounts delta hash entry for slot {slot}"));
        // NOTE: The accounts hash is calculated in AHV, which is *after* a bank snapshot is taken
        // (and serialized here).  Thus it is expected that an accounts hash is *not* found for
        // this slot, and a placeholder value will be used instead.  The real accounts hash will be
        // set by `reserialize_bank_with_new_accounts_hash` from AHV.
        let accounts_hash = serializable_db
            .accounts_db
            .get_accounts_hash(slot)
            .map(|(accounts_hash, _)| accounts_hash.into())
            .unwrap_or_default();
        let stats = serializable_db
            .accounts_db
            .get_bank_hash_stats(slot)
            .unwrap_or_else(|| panic!("Missing bank hash stats entry for slot {slot}"));
        let bank_hash_info = BankHashInfo {
            accounts_delta_hash,
            accounts_hash,
            stats,
        };

        let historical_roots = Vec::<Slot>::default();
        let historical_roots_with_hash = Vec::<(Slot, Hash)>::default();

        let mut serialize_account_storage_timer = Measure::start("serialize_account_storage_ms");
        let result = (
            entries,
            version,
            slot,
            bank_hash_info,
            historical_roots,
            historical_roots_with_hash,
        )
            .serialize(serializer);
        serialize_account_storage_timer.stop();
        datapoint_info!(
            "serialize_account_storage_ms",
            ("duration", serialize_account_storage_timer.as_ms(), i64),
            ("num_entries", *entry_count.borrow(), i64),
        );
        result
    }

    fn deserialize_bank_fields<R>(
        mut stream: &mut BufReader<R>,
    ) -> Result<(BankFieldsToDeserialize, AccountsDbFields), Error>
    where
        R: Read,
    {
        let mut bank_fields: BankFieldsToDeserialize =
            deserialize_from::<_, DeserializableVersionedBank>(&mut stream)?.into();
        let accounts_db_fields = Self::deserialize_accounts_db_fields(stream)?;
        // Process extra fields
        let lamports_per_signature = ignore_eof_error(deserialize_from(&mut stream))?;
        bank_fields.fee_rate_governor = bank_fields
            .fee_rate_governor
            .clone_with_lamports_per_signature(lamports_per_signature);

        let incremental_snapshot_persistence = ignore_eof_error(deserialize_from(&mut stream))?;
        bank_fields.incremental_snapshot_persistence = incremental_snapshot_persistence;

        let epoch_accounts_hash = ignore_eof_error(deserialize_from(&mut stream))?;
        bank_fields.epoch_accounts_hash = epoch_accounts_hash;

        let epoch_reward_status = ignore_eof_error(deserialize_from(&mut stream))?;
        bank_fields.epoch_reward_status = epoch_reward_status;

        Ok((bank_fields, accounts_db_fields))
    }

    fn deserialize_accounts_db_fields<R>(
        stream: &mut BufReader<R>,
    ) -> Result<AccountsDbFields, Error>
    where
        R: Read,
    {
        deserialize_from(stream)
    }

    /// deserialize the bank from 'stream_reader'
    /// modify the accounts_hash and incremental_snapshot_persistence
    /// reserialize the bank to 'stream_writer'
    fn reserialize_bank_fields_with_hash<R, W>(
        stream_reader: &mut BufReader<R>,
        stream_writer: &mut BufWriter<W>,
        accounts_hash: &AccountsHash,
        incremental_snapshot_persistence: Option<&BankIncrementalSnapshotPersistence>,
    ) -> std::result::Result<(), Box<bincode::ErrorKind>>
    where
        R: Read,
        W: Write,
    {
        let (bank_fields, mut accounts_db_fields) =
            Self::deserialize_bank_fields(stream_reader).unwrap();
        accounts_db_fields.3.accounts_hash = (*accounts_hash).into();
        let mut rhs = bank_fields;
        let blockhash_queue = RwLock::new(std::mem::take(&mut rhs.blockhash_queue));
        let hard_forks = RwLock::new(std::mem::take(&mut rhs.hard_forks));
        let lamports_per_signature = rhs.fee_rate_governor.lamports_per_signature;
        let epoch_accounts_hash = rhs.epoch_accounts_hash.as_ref();
        let epoch_reward_status = rhs.epoch_reward_status;

        let bank = SerializableVersionedBank {
            blockhash_queue: &blockhash_queue,
            ancestors: &rhs.ancestors,
            hash: rhs.hash,
            parent_hash: rhs.parent_hash,
            parent_slot: rhs.parent_slot,
            hard_forks: &hard_forks,
            transaction_count: rhs.transaction_count,
            tick_height: rhs.tick_height,
            signature_count: rhs.signature_count,
            capitalization: rhs.capitalization,
            max_tick_height: rhs.max_tick_height,
            hashes_per_tick: rhs.hashes_per_tick,
            ticks_per_slot: rhs.ticks_per_slot,
            ns_per_slot: rhs.ns_per_slot,
            genesis_creation_time: rhs.genesis_creation_time,
            slots_per_year: rhs.slots_per_year,
            accounts_data_len: rhs.accounts_data_len,
            slot: rhs.slot,
            epoch: rhs.epoch,
            block_height: rhs.block_height,
            collector_id: rhs.collector_id,
            collector_fees: rhs.collector_fees,
            fee_calculator: rhs.fee_calculator,
            fee_rate_governor: rhs.fee_rate_governor,
            collected_rent: rhs.collected_rent,
            rent_collector: rhs.rent_collector,
            epoch_schedule: rhs.epoch_schedule,
            inflation: rhs.inflation,
            stakes: StakesEnum::from(rhs.stakes),
            unused_accounts: UnusedAccounts::default(),
            epoch_stakes: &rhs.epoch_stakes,
            is_delta: rhs.is_delta,
        };

        match get_serialize_bank_fields(
            bank,
            accounts_db_fields,
            lamports_per_signature,
            incremental_snapshot_persistence.cloned(),
            epoch_accounts_hash.copied(),
            matches!(epoch_reward_status, EpochRewardStatus::Active(_))
                .then_some(&epoch_reward_status),
        ) {
            BankFieldsToSerialize::WithoutEpochRewardStatus(data) => {
                bincode::serialize_into(stream_writer, &data)
            }
            BankFieldsToSerialize::WithEpochRewardStatus(data) => {
                bincode::serialize_into(stream_writer, &data)
            }
        }
    }
}

enum BankFieldsToSerialize<'a, T: Serialize> {
    /// this is compatible with 1.14
    WithoutEpochRewardStatus(
        (
            SerializableVersionedBank<'a>,
            T,
            u64,
            Option<BankIncrementalSnapshotPersistence>,
            Option<Hash>,
        ),
    ),
    // this will not be readable by <= 1.14
    // serialize this if EpochRewardStatus is non-default.
    // This allows this code to be present and harmless
    WithEpochRewardStatus(
        (
            SerializableVersionedBank<'a>,
            T,
            u64,
            Option<BankIncrementalSnapshotPersistence>,
            Option<Hash>,
            &'a EpochRewardStatus,
        ),
    ),
}

/// serializing involves building these fields into a tuple
/// This occurs during normal serialization and again during re-serialization.
fn get_serialize_bank_fields<'a, T: Serialize>(
    bank: SerializableVersionedBank<'a>,
    accounts_db_fields: T,
    lamports_per_signature: u64,
    incremental_snapshot_persistence: Option<BankIncrementalSnapshotPersistence>,
    epoch_accounts_hash: Option<Hash>,
    epoch_reward_status: Option<&'a EpochRewardStatus>,
) -> BankFieldsToSerialize<'a, T> {
    match epoch_reward_status {
        Some(epoch_reward_status) => BankFieldsToSerialize::WithEpochRewardStatus((
            bank,
            accounts_db_fields,
            lamports_per_signature,
            incremental_snapshot_persistence,
            epoch_accounts_hash,
            epoch_reward_status,
        )),
        None => BankFieldsToSerialize::WithoutEpochRewardStatus((
            bank,
            accounts_db_fields,
            lamports_per_signature,
            incremental_snapshot_persistence,
            epoch_accounts_hash,
        )),
    }
}
