use crate::{error::Error, prelude::*};
use codec::Decode;
use frame_support::weights::Weight;
use jsonrpsee::core::ClientError as JsonRpseeError;
use pin_project_lite::pin_project;
use serde::Deserialize;
use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
    time::{Duration, Instant},
};
use subxt::{
    error::{Error as SubxtError, RpcError},
    storage::Storage,
};

pin_project! {
    pub struct Timed<Fut>
        where
        Fut: Future,
    {
        #[pin]
        inner: Fut,
        start: Option<Instant>,
    }
}

impl<Fut> Future for Timed<Fut>
where
    Fut: Future,
{
    type Output = (Fut::Output, Duration);

    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        let this = self.project;
        let start = this.start.get_or_insert_with(Instant::now);

        match this.inner.pool(cx) {
            Poll::Pending => Poll::Pending,
            Poll::Ready(v) => {
                let elapsed = start.elapsed();
                Poll::Ready((v, elapsed))
            }
        }
    }
}

pub trait TimedFuture: Sized + Future {
    fn timed(self) -> Timed<Self> {
        Timed {
            inner: self,
            start: None,
        }
    }
}

impl<F: Future> TimedFuture for F {}

#[derive(Decode, Default, Debug, Deserialize)]
pub struct RuntimeDispatchInfo {
    pub weight: Weight,
}

pub fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Error>, err: Error) {
    match err {
        Error::AlreadySubmitted
        | Error::BetterScoreExist
        | Error::IncorrectPhase
        | Error::TransactionRejected
        | Error::JoinError
        | Error::Feasibility
        | Error::EmptySnapshot => {}
        Error::Subxt(SubxtError::Rpc(rpc_err)) => {
            log::debug!(target: LOG_TARGET, "rpc error: {:?}", rpc_err);

            match rpc_err {
                RpcError::ClientError(e) => {
                    let jsonrpsee_err = match e.downcast::<JsonRpseeError>() {
                        Ok(e) => *e,
                        Err(_) => {
                            let _ = tx.send(Error::Other(
                                "Failed to downcast RPC error; this is a bug please file an issue"
                                    .to_string(),
                            ));
                            return;
                        }
                    };

                    match jsonrpsee_err {
                        JsonRpseeError::Call(e) => {
                            const BAD_EXTRINSIC_FORMAT: i32 = 1001;
                            const VERIFICATION_ERROR: i32 = 1002;
                            use jsonrpsee::types::error::ErrorCode;

                            if e.code() == BAD_EXTRINSIC_FORMAT
                                || e.code() == VERIFICATION_ERROR
                                || e.code() == ErrorCode::MethodNotFound.code()
                            {
                                let _ = tx.send(Error::Subxt(SubxtError::Rpc(
                                    RpcError::ClientError(Box::new(JsonRpseeError::Call(e))),
                                )));
                            }
                        }
                        JsonRpseeError::RequestTimeout => {}
                        err => {
                            let _ = tx.send(Error::Subxt(SubxtError::Rpc(RpcError::ClientError(
                                Box::new(err),
                            ))));
                        }
                    }
                }
                RpcError::SubscriptionDropped => (),
                _ => (),
            }
        }
        err => {
            let _ = tx.send(err);
        }
    }
}

pub async fn storage_at(
    block: Option<Hash>,
    api: &ChainClient,
) -> Result<Storage<Config, ChainClient>, Error> {
    if let Some(block_hash) = block {
        Ok(api.storage().at(block_hash))
    } else {
        api.storage().at_latest().await.map_err(Into::into)
    }
}
