use std::{
    fmt::{Debug, Display, Error as FmtError, Formatter},
    hash::Hash,
    path::PathBuf,
    sync::Arc,
};
use derive_more::Display;
use futures::{
    channel::{mpsc, oneshot},
    Future,
};
use parity_scale_codec::{Decode, Encode, Output};
use core_primitives as phron_primitives;
use core_primitives::{AuthorityId, Block as AlephBlock, BlockHash, BlockNumber, Hash as AlephHash};
use sc_client_api::{
    Backend, BlockBackend, BlockchainEvents, Finalizer, LockImportRun, StorageProvider,
};
use sc_consensus::BlockImport;
use sc_network::NetworkService;
use sc_network_sync::SyncingService;
use sp_api::ProvideRuntimeApi;
use sp_blockchain::{HeaderBackend, HeaderMetadata};
use sp_keystore::Keystore;
use sp_runtime::traits::{BlakeTwo256, Block};
use substrate_prometheus_endpoint::Registry;
use tokio::time::Duration;
use crate::{
    abft::{
        CurrentNetworkData, Keychain, LegacyNetworkData, NodeCount, NodeIndex, Recipient,
        SignatureSet, SpawnHandle, CURRENT_VERSION, LEGACY_VERSION,
    },
    aggregation::{CurrentRmcNetworkData, LegacyRmcNetworkData},
    compatibility::{Version, Versioned},
    network::data::split::Split,
    session::{SessionBoundaries, SessionBoundaryInfo, SessionId},
    VersionedTryFromError::{ExpectedNewGotOld, ExpectedOldGotNew},
};
mod abft;
mod aggregation;
mod compatibility;
mod crypto;
mod data_io;
mod finalization;
mod idx_to_account;
mod import;
mod justification;
mod metrics;
mod network;
mod nodes;
mod party;
mod runtime_api;
mod session;
mod session_map;
mod sync;
mod sync_oracle;
#[cfg(test)]
pub mod testing;
pub use crate::{
    import::{PhronBlockImport, RedirectingBlockImport, TracingBlockImport},
    justification::PhronJustification,
    metrics::TimingBlockMetrics,
    network::{
        address_cache::{ValidatorAddressCache, ValidatorAddressingInfo}, Protocol, ProtocolNaming,
    },
    nodes::run_validator_node,
    session::SessionPeriod,
    sync::{
        substrate::{BlockImporter, Justification},
        JustificationTranslator, SubstrateChainStatus,
    },
    sync_oracle::SyncOracle,
};
const STATUS_REPORT_INTERVAL: Duration = Duration::from_secs(20);
pub fn peers_set_config(
    naming: ProtocolNaming,
    protocol: Protocol,
) -> sc_network::config::NonDefaultSetConfig {
    let mut config = sc_network::config::NonDefaultSetConfig::new(
        naming.protocol_name(&protocol),
        1024 * 1024,
    );
    config.set_config = sc_network::config::SetConfig::default();
    config.add_fallback_names(naming.fallback_protocol_names(&protocol));
    config
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)]
pub struct MillisecsPerBlock(pub u64);
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)]
pub struct UnitCreationDelay(pub u64);
type LegacySplitData = Split<LegacyNetworkData, LegacyRmcNetworkData>;
type CurrentSplitData = Split<CurrentNetworkData, CurrentRmcNetworkData>;
impl Versioned for LegacyNetworkData {
    const VERSION: Version = Version(LEGACY_VERSION);
}
impl Versioned for CurrentNetworkData {
    const VERSION: Version = Version(CURRENT_VERSION);
}
#[derive(Clone)]
pub enum VersionedEitherMessage<L, R> {
    Left(L),
    Right(R),
}
impl<L: Versioned + Decode, R: Versioned + Decode> Decode for VersionedEitherMessage<L, R> {
    fn decode<I: parity_scale_codec::Input>(
        input: &mut I,
    ) -> Result<Self, parity_scale_codec::Error> {
        let version = Version::decode(input)?;
        if version == L::VERSION {
            return Ok(VersionedEitherMessage::Left(L::decode(input)?));
        }
        if version == R::VERSION {
            return Ok(VersionedEitherMessage::Right(R::decode(input)?));
        }
        Err("Invalid version while decoding VersionedEitherMessage".into())
    }
}
impl<L: Versioned + Encode, R: Versioned + Encode> Encode for VersionedEitherMessage<L, R> {
    fn size_hint(&self) -> usize {
        match self {
            VersionedEitherMessage::Left(left) => L::VERSION.size_hint() + left.size_hint(),
            VersionedEitherMessage::Right(right) => R::VERSION.size_hint() + right.size_hint(),
        }
    }
    fn encode_to<T: Output + ?Sized>(&self, dest: &mut T) {
        match self {
            VersionedEitherMessage::Left(left) => {
                L::VERSION.encode_to(dest);
                left.encode_to(dest);
            }
            VersionedEitherMessage::Right(right) => {
                R::VERSION.encode_to(dest);
                right.encode_to(dest);
            }
        }
    }
}
type VersionedNetworkData = VersionedEitherMessage<LegacySplitData, CurrentSplitData>;
#[derive(Debug, Display, Clone)]
pub enum VersionedTryFromError {
    ExpectedNewGotOld,
    ExpectedOldGotNew,
}
impl TryFrom<VersionedNetworkData> for LegacySplitData {
    type Error = VersionedTryFromError;
    fn try_from(value: VersionedNetworkData) -> Result<Self, Self::Error> {
        Ok(match value {
            VersionedEitherMessage::Left(data) => data,
            VersionedEitherMessage::Right(_) => return Err(ExpectedOldGotNew),
        })
    }
}
impl TryFrom<VersionedNetworkData> for CurrentSplitData {
    type Error = VersionedTryFromError;
    fn try_from(value: VersionedNetworkData) -> Result<Self, Self::Error> {
        Ok(match value {
            VersionedEitherMessage::Left(_) => return Err(ExpectedNewGotOld),
            VersionedEitherMessage::Right(data) => data,
        })
    }
}
impl From<LegacySplitData> for VersionedNetworkData {
    fn from(data: LegacySplitData) -> Self {
        VersionedEitherMessage::Left(data)
    }
}
impl From<CurrentSplitData> for VersionedNetworkData {
    fn from(data: CurrentSplitData) -> Self {
        VersionedEitherMessage::Right(data)
    }
}
pub trait ClientForPhron<B, BE>:
    LockImportRun<B, BE>
    + Finalizer<B, BE>
    + ProvideRuntimeApi<B>
    + BlockImport<B, Error = sp_consensus::Error>
    + HeaderBackend<B>
    + HeaderMetadata<B, Error = sp_blockchain::Error>
    + BlockchainEvents<B>
    + BlockBackend<B>
    + StorageProvider<B, BE>
where
    BE: Backend<B>,
    B: Block,
{
}
impl<B, BE, T> ClientForPhron<B, BE> for T
where
    BE: Backend<B>,
    B: Block,
    T: LockImportRun<B, BE>
        + Finalizer<B, BE>
        + ProvideRuntimeApi<B>
        + HeaderBackend<B>
        + HeaderMetadata<B, Error = sp_blockchain::Error>
        + BlockchainEvents<B>
        + BlockImport<B, Error = sp_consensus::Error>
        + BlockBackend<B>
        + StorageProvider<B, BE>,
{
}
type Hasher = abft::HashWrapper<BlakeTwo256>;
#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode, Hash)]
pub struct BlockId {
    hash: BlockHash,
    number: BlockNumber,
}
impl BlockId {
    pub fn new(hash: BlockHash, number: BlockNumber) -> Self {
        BlockId { hash, number }
    }
    pub fn number(&self) -> BlockNumber {
        self.number
    }
}
impl From<(BlockHash, BlockNumber)> for BlockId {
    fn from(pair: (BlockHash, BlockNumber)) -> Self {
        BlockId::new(pair.0, pair.1)
    }
}
impl Display for BlockId {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        write!(f, "#{} ({})", self.number, self.hash,)
    }
}
#[derive(Clone)]
pub struct RateLimiterConfig {
    pub phron_bit_rate_per_connection: usize,
}
pub struct PhronConfig<C, SC> {
    pub network: Arc<NetworkService<AlephBlock, AlephHash>>,
    pub sync_network: Arc<SyncingService<AlephBlock>>,
    pub client: Arc<C>,
    pub chain_status: SubstrateChainStatus,
    pub import_queue_handle: BlockImporter,
    pub select_chain: SC,
    pub spawn_handle: SpawnHandle,
    pub keystore: Arc<dyn Keystore>,
    pub justification_rx: mpsc::UnboundedReceiver<Justification>,
    pub block_rx: mpsc::UnboundedReceiver<AlephBlock>,
    pub metrics: TimingBlockMetrics,
    pub registry: Option<Registry>,
    pub session_period: SessionPeriod,
    pub millisecs_per_block: MillisecsPerBlock,
    pub unit_creation_delay: UnitCreationDelay,
    pub backup_saving_path: Option<PathBuf>,
    pub external_addresses: Vec<String>,
    pub validator_port: u16,
    pub protocol_naming: ProtocolNaming,
    pub rate_limiter_config: RateLimiterConfig,
    pub sync_oracle: SyncOracle,
    pub validator_address_cache: Option<ValidatorAddressCache>,
}