use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256};
use reth_rpc_types::{
    state::StateOverride,
    trace::geth::{
        BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
        TraceResult,
    },
    Bundle, CallRequest, RichBlock, StateContext,
};

/// Debug rpc interface.
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "debug"))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "debug"))]
pub trait DebugApi {
    /// Returns an RLP-encoded header.
    #[method(name = "getRawHeader")]
    async fn raw_header(&self, block_id: BlockId) -> RpcResult<Bytes>;

    /// Returns an RLP-encoded block.
    #[method(name = "getRawBlock")]
    async fn raw_block(&self, block_id: BlockId) -> RpcResult<Bytes>;

    /// Returns a EIP-2718 binary-encoded transaction.
    #[method(name = "getRawTransaction")]
    async fn raw_transaction(&self, hash: H256) -> RpcResult<Bytes>;

    /// Returns an array of EIP-2718 binary-encoded receipts.
    #[method(name = "getRawReceipts")]
    async fn raw_receipts(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>>;

    /// Returns an array of recent bad blocks that the client has seen on the network.
    #[method(name = "getBadBlocks")]
    async fn bad_blocks(&self) -> RpcResult<Vec<RichBlock>>;

    /// Returns the structured logs created during the execution of EVM between two blocks
    /// (excluding start) as a JSON object.
    #[method(name = "traceChain")]
    async fn debug_trace_chain(
        &self,
        start_exclusive: BlockNumberOrTag,
        end_inclusive: BlockNumberOrTag,
    ) -> RpcResult<Vec<BlockTraceResult>>;

    /// The `debug_traceBlock` method will return a full stack trace of all invoked opcodes of all
    /// transaction that were included in this block.
    ///
    /// This expects an rlp encoded block
    ///
    /// Note, the parent of this block must be present, or it will fail. For the second parameter
    /// see [GethDebugTracingOptions] reference.
    #[method(name = "traceBlock")]
    async fn debug_trace_block(
        &self,
        rlp_block: Bytes,
        opts: Option<GethDebugTracingOptions>,
    ) -> RpcResult<Vec<TraceResult>>;

    /// Similar to `debug_traceBlock`, `debug_traceBlockByHash` accepts a block hash and will replay
    /// the block that is already present in the database. For the second parameter see
    /// [GethDebugTracingOptions].
    #[method(name = "traceBlockByHash")]
    async fn debug_trace_block_by_hash(
        &self,
        block: H256,
        opts: Option<GethDebugTracingOptions>,
    ) -> RpcResult<Vec<TraceResult>>;

    /// Similar to `debug_traceBlockByHash`, `debug_traceBlockByNumber` accepts a block number
    /// [BlockNumberOrTag] and will replay the block that is already present in the database.
    /// For the second parameter see [GethDebugTracingOptions].
    #[method(name = "traceBlockByNumber")]
    async fn debug_trace_block_by_number(
        &self,
        block: BlockNumberOrTag,
        opts: Option<GethDebugTracingOptions>,
    ) -> RpcResult<Vec<TraceResult>>;

    /// The `debug_traceTransaction` debugging method will attempt to run the transaction in the
    /// exact same manner as it was executed on the network. It will replay any transaction that
    /// may have been executed prior to this one before it will finally attempt to execute the
    /// transaction that corresponds to the given hash.
    #[method(name = "traceTransaction")]
    async fn debug_trace_transaction(
        &self,
        tx_hash: H256,
        opts: Option<GethDebugTracingOptions>,
    ) -> RpcResult<GethTrace>;

    /// The `debug_traceCall` method lets you run an `eth_call` within the context of the given
    /// block execution using the final state of parent block as the base.
    ///
    /// The first argument (just as in `eth_call`) is a transaction request.
    /// The block can optionally be specified either by hash or by number as
    /// the second argument.
    /// The trace can be configured similar to `debug_traceTransaction`,
    /// see [GethDebugTracingOptions]. The method returns the same output as
    /// `debug_traceTransaction`.
    #[method(name = "traceCall")]
    async fn debug_trace_call(
        &self,
        request: CallRequest,
        block_number: Option<BlockId>,
        opts: Option<GethDebugTracingCallOptions>,
    ) -> RpcResult<GethTrace>;

    /// The `debug_traceCallMany` method lets you run an `eth_callmany` within the context of the
    /// given block execution using the final state of parent block as the base followed by n
    /// transactions
    ///
    /// The first argument is a list of bundles. Each bundle can overwrite the block headers. This
    /// will affect all transaction in that bundle.
    /// BlockNumber and transaction_index are optinal. Transaction_index
    /// specifys the number of tx in the block to replay and -1 means all transactions should be
    /// replayed.
    /// The trace can be configured similar to `debug_traceTransaction`.
    /// State override apply to all bundles.
    #[method(name = "traceCallMany")]
    async fn debug_trace_call_many(
        &self,
        bundles: Vec<Bundle>,
        state_context: Option<StateContext>,
        opts: Option<GethDebugTracingOptions>,
        state_override: Option<StateOverride>,
    ) -> RpcResult<Vec<GethTrace>>;
}
