use std::sync::Arc;
use std::collections::{BTreeMap, HashMap};
use std::future::Future;
use sp_runtime::{
	traits::{
		Block as BlockT, Header as HeaderT, NumberFor,
	},
	generic::BlockId
};
use sp_core::{ChangesTrieConfigurationRange, storage::PrefixedStorageKey};
use sp_state_machine::StorageProof;
use sp_blockchain::{
	HeaderMetadata, well_known_cache_keys, HeaderBackend, Cache as BlockchainCache,
	Error as ClientError, Result as ClientResult,
};
use crate::{backend::{AuxStore, NewBlockState}, UsageInfo, ProvideChtRoots};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteCallRequest<Header: HeaderT> {
	
	pub block: Header::Hash,
	
	pub header: Header,
	
	pub method: String,
	
	pub call_data: Vec<u8>,
	
	pub retry_count: Option<usize>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct RemoteHeaderRequest<Header: HeaderT> {
	
	pub cht_root: Header::Hash,
	
	pub block: Header::Number,
	
	pub retry_count: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteReadRequest<Header: HeaderT> {
	
	pub block: Header::Hash,
	
	pub header: Header,
	
	pub keys: Vec<Vec<u8>>,
	
	pub retry_count: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RemoteReadChildRequest<Header: HeaderT> {
	
	pub block: Header::Hash,
	
	pub header: Header,
	
	pub storage_key: PrefixedStorageKey,
	
	pub keys: Vec<Vec<u8>>,
	
	pub retry_count: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RemoteChangesRequest<Header: HeaderT> {
	
	pub changes_trie_configs: Vec<ChangesTrieConfigurationRange<Header::Number, Header::Hash>>,
	
	pub first_block: (Header::Number, Header::Hash),
	
	
	pub last_block: (Header::Number, Header::Hash),
	
	
	pub max_block: (Header::Number, Header::Hash),
	
	
	pub tries_roots: (Header::Number, Header::Hash, Vec<Header::Hash>),
	
	pub storage_key: Option<PrefixedStorageKey>,
	
	pub key: Vec<u8>,
	
	pub retry_count: Option<usize>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChangesProof<Header: HeaderT> {
	
	pub max_block: Header::Number,
	
	pub proof: Vec<Vec<u8>>,
	
	
	pub roots: BTreeMap<Header::Number, Header::Hash>,
	
	
	pub roots_proof: StorageProof,
}
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub struct RemoteBodyRequest<Header: HeaderT> {
	
	pub header: Header,
	
	pub retry_count: Option<usize>,
}
pub trait Fetcher<Block: BlockT>: Send + Sync {
	
	type RemoteHeaderResult: Future<Output = Result<
		Block::Header,
		ClientError,
	>> + Unpin + Send + 'static;
	
	type RemoteReadResult: Future<Output = Result<
		HashMap<Vec<u8>, Option<Vec<u8>>>,
		ClientError,
	>> + Unpin + Send + 'static;
	
	type RemoteCallResult: Future<Output = Result<
		Vec<u8>,
		ClientError,
	>> + Unpin + Send + 'static;
	
	type RemoteChangesResult: Future<Output = Result<
		Vec<(NumberFor<Block>, u32)>,
		ClientError,
	>> + Unpin + Send + 'static;
	
	type RemoteBodyResult: Future<Output = Result<
		Vec<Block::Extrinsic>,
		ClientError,
	>> + Unpin + Send + 'static;
	
	fn remote_header(&self, request: RemoteHeaderRequest<Block::Header>) -> Self::RemoteHeaderResult;
	
	fn remote_read(
		&self,
		request: RemoteReadRequest<Block::Header>
	) -> Self::RemoteReadResult;
	
	fn remote_read_child(
		&self,
		request: RemoteReadChildRequest<Block::Header>
	) -> Self::RemoteReadResult;
	
	fn remote_call(&self, request: RemoteCallRequest<Block::Header>) -> Self::RemoteCallResult;
	
	
	fn remote_changes(&self, request: RemoteChangesRequest<Block::Header>) -> Self::RemoteChangesResult;
	
	fn remote_body(&self, request: RemoteBodyRequest<Block::Header>) -> Self::RemoteBodyResult;
}
pub trait FetchChecker<Block: BlockT>: Send + Sync {
	
	fn check_header_proof(
		&self,
		request: &RemoteHeaderRequest<Block::Header>,
		header: Option<Block::Header>,
		remote_proof: StorageProof,
	) -> ClientResult<Block::Header>;
	
	fn check_read_proof(
		&self,
		request: &RemoteReadRequest<Block::Header>,
		remote_proof: StorageProof,
	) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>>;
	
	fn check_read_child_proof(
		&self,
		request: &RemoteReadChildRequest<Block::Header>,
		remote_proof: StorageProof,
	) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>>;
	
	fn check_execution_proof(
		&self,
		request: &RemoteCallRequest<Block::Header>,
		remote_proof: StorageProof,
	) -> ClientResult<Vec<u8>>;
	
	fn check_changes_proof(
		&self,
		request: &RemoteChangesRequest<Block::Header>,
		proof: ChangesProof<Block::Header>
	) -> ClientResult<Vec<(NumberFor<Block>, u32)>>;
	
	fn check_body_proof(
		&self,
		request: &RemoteBodyRequest<Block::Header>,
		body: Vec<Block::Extrinsic>
	) -> ClientResult<Vec<Block::Extrinsic>>;
}
pub trait Storage<Block: BlockT>: AuxStore + HeaderBackend<Block>
	+ HeaderMetadata<Block, Error=ClientError> + ProvideChtRoots<Block>
{
	
	
	
	
	fn import_header(
		&self,
		header: Block::Header,
		cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
		state: NewBlockState,
		aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
	) -> ClientResult<()>;
	
	fn set_head(&self, block: BlockId<Block>) -> ClientResult<()>;
	
	fn finalize_header(&self, block: BlockId<Block>) -> ClientResult<()>;
	
	fn last_finalized(&self) -> ClientResult<Block::Hash>;
	
	fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>>;
	
	fn usage_info(&self) -> Option<UsageInfo>;
}
#[derive(Debug)]
pub enum LocalOrRemote<Data, Request> {
	
	Local(Data),
	
	Remote(Request),
	
	Unknown,
}
pub trait RemoteBlockchain<Block: BlockT>: Send + Sync {
	
	fn header(&self, id: BlockId<Block>) -> ClientResult<LocalOrRemote<
		Block::Header,
		RemoteHeaderRequest<Block::Header>,
	>>;
}
pub fn future_header<Block: BlockT, F: Fetcher<Block>>(
	blockchain: &dyn RemoteBlockchain<Block>,
	fetcher: &F,
	id: BlockId<Block>,
) -> impl Future<Output = Result<Option<Block::Header>, ClientError>> {
	use futures::future::{ready, Either, FutureExt};
	match blockchain.header(id) {
		Ok(LocalOrRemote::Remote(request)) => Either::Left(
			fetcher
				.remote_header(request)
				.then(|header| ready(header.map(Some)))
		),
		Ok(LocalOrRemote::Unknown) => Either::Right(ready(Ok(None))),
		Ok(LocalOrRemote::Local(local_header)) => Either::Right(ready(Ok(Some(local_header)))),
		Err(err) => Either::Right(ready(Err(err))),
	}
}
#[cfg(test)]
pub mod tests {
	use futures::future::Ready;
	use parking_lot::Mutex;
	use sp_blockchain::Error as ClientError;
	use sp_test_primitives::{Block, Header, Extrinsic};
	use super::*;
	#[derive(Debug, thiserror::Error)]
	#[error("Not implemented on test node")]
	struct MockError;
	impl Into<ClientError> for MockError {
		fn into(self) -> ClientError {
			ClientError::Application(Box::new(self))
		}
	}	
	
	pub type OkCallFetcher = Mutex<Vec<u8>>;
	fn not_implemented_in_tests<T>() -> Ready<Result<T, ClientError>>
	{
		futures::future::ready(Err(MockError.into()))
	}
	impl Fetcher<Block> for OkCallFetcher {
		type RemoteHeaderResult = Ready<Result<Header, ClientError>>;
		type RemoteReadResult = Ready<Result<HashMap<Vec<u8>, Option<Vec<u8>>>, ClientError>>;
		type RemoteCallResult = Ready<Result<Vec<u8>, ClientError>>;
		type RemoteChangesResult = Ready<Result<Vec<(NumberFor<Block>, u32)>, ClientError>>;
		type RemoteBodyResult = Ready<Result<Vec<Extrinsic>, ClientError>>;
		fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {
			not_implemented_in_tests()
		}
		fn remote_read(&self, _request: RemoteReadRequest<Header>) -> Self::RemoteReadResult {
			not_implemented_in_tests()
		}
		fn remote_read_child(&self, _request: RemoteReadChildRequest<Header>) -> Self::RemoteReadResult {
			not_implemented_in_tests()
		}
		fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
			futures::future::ready(Ok((*self.lock()).clone()))
		}
		fn remote_changes(&self, _request: RemoteChangesRequest<Header>) -> Self::RemoteChangesResult {
			not_implemented_in_tests()
		}
		fn remote_body(&self, _request: RemoteBodyRequest<Header>) -> Self::RemoteBodyResult {
			not_implemented_in_tests()
		}
	}
}