//! Available RPC methods types and ids.

use crate::types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield};
use regex::bytes::Regex;
use serde::Serialize;
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use ssz_types::{RuntimeVariableList, VariableList, typenum::U256};
use std::fmt::Display;
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
use strum::IntoStaticStr;
use superstruct::superstruct;
use types::data::BlobIdentifier;
use types::light_client::consts::MAX_REQUEST_LIGHT_CLIENT_UPDATES;
use types::{
    BlobSidecar, ChainSpec, ColumnIndex, DataColumnSidecar, DataColumnsByRootIdentifier, Epoch,
    EthSpec, ForkContext, Hash256, LightClientBootstrap, LightClientFinalityUpdate,
    LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock,
    SignedExecutionPayloadEnvelope, Slot,
};

/// Maximum length of error message.
pub type MaxErrorLen = U256;
pub const MAX_ERROR_LEN: u64 = 256;

/// Wrapper over SSZ List to represent error message in rpc responses.
#[derive(Debug, Clone)]
pub struct ErrorType(pub VariableList<u8, MaxErrorLen>);

impl From<&str> for ErrorType {
    // This will truncate the error if `string.as_bytes()` exceeds `MaxErrorLen`.
    fn from(s: &str) -> Self {
        let mut bytes = s.as_bytes().to_vec();
        bytes.truncate(MAX_ERROR_LEN as usize);
        Self(
            VariableList::try_from(bytes)
                .expect("length should not exceed MaxErrorLen after truncation"),
        )
    }
}

impl From<String> for ErrorType {
    fn from(s: String) -> Self {
        Self::from(s.as_str())
    }
}

impl Deref for ErrorType {
    type Target = VariableList<u8, MaxErrorLen>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Display for ErrorType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        #[allow(clippy::invalid_regex)]
        let re = Regex::new("\\p{C}").expect("Regex is valid");
        let error_type_str =
            String::from_utf8_lossy(&re.replace_all(self.0.deref(), &b""[..])).to_string();
        write!(f, "{}", error_type_str)
    }
}

/* Request/Response data structures for RPC methods */

/* Requests */

/// The STATUS request/response handshake message.
#[superstruct(
    variants(V1, V2),
    variant_attributes(derive(Encode, Decode, Clone, Debug, PartialEq),)
)]
#[derive(Clone, Debug, PartialEq)]
pub struct StatusMessage {
    /// The fork version of the chain we are broadcasting.
    pub fork_digest: [u8; 4],

    /// Latest finalized root.
    pub finalized_root: Hash256,

    /// Latest finalized epoch.
    pub finalized_epoch: Epoch,

    /// The latest block root.
    pub head_root: Hash256,

    /// The slot associated with the latest block root.
    pub head_slot: Slot,

    /// The slot after which we guarantee to have all the blocks
    /// and blobs/data columns that we currently advertise.
    #[superstruct(only(V2))]
    pub earliest_available_slot: Slot,
}

impl StatusMessage {
    pub fn status_v1(&self) -> StatusMessageV1 {
        match &self {
            Self::V1(status) => status.clone(),
            Self::V2(status) => StatusMessageV1 {
                fork_digest: status.fork_digest,
                finalized_root: status.finalized_root,
                finalized_epoch: status.finalized_epoch,
                head_root: status.head_root,
                head_slot: status.head_slot,
            },
        }
    }

    pub fn status_v2(&self) -> StatusMessageV2 {
        match &self {
            Self::V1(status) => StatusMessageV2 {
                fork_digest: status.fork_digest,
                finalized_root: status.finalized_root,
                finalized_epoch: status.finalized_epoch,
                head_root: status.head_root,
                head_slot: status.head_slot,
                // Note: we always produce a V2 message as our local
                // status message, so this match arm should ideally never
                // be invoked in lighthouse.
                earliest_available_slot: Slot::new(0),
            },
            Self::V2(status) => status.clone(),
        }
    }
}

/// The PING request/response message.
#[derive(Encode, Decode, Copy, Clone, Debug, PartialEq)]
pub struct Ping {
    /// The metadata sequence number.
    pub data: u64,
}

/// The METADATA request structure.
#[superstruct(
    variants(V1, V2, V3),
    variant_attributes(derive(Clone, Debug, PartialEq, Serialize),)
)]
#[derive(Clone, Debug, PartialEq)]
pub struct MetadataRequest<E: EthSpec> {
    _phantom_data: PhantomData<E>,
}

impl<E: EthSpec> MetadataRequest<E> {
    pub fn new_v1() -> Self {
        Self::V1(MetadataRequestV1 {
            _phantom_data: PhantomData,
        })
    }

    pub fn new_v2() -> Self {
        Self::V2(MetadataRequestV2 {
            _phantom_data: PhantomData,
        })
    }

    pub fn new_v3() -> Self {
        Self::V3(MetadataRequestV3 {
            _phantom_data: PhantomData,
        })
    }
}

/// The METADATA response structure.
#[superstruct(
    variants(V1, V2, V3),
    variant_attributes(
        derive(Encode, Decode, Clone, Debug, PartialEq, Serialize),
        serde(bound = "E: EthSpec", deny_unknown_fields),
    )
)]
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(bound = "E: EthSpec")]
pub struct MetaData<E: EthSpec> {
    /// A sequential counter indicating when data gets modified.
    pub seq_number: u64,
    /// The persistent attestation subnet bitfield.
    pub attnets: EnrAttestationBitfield<E>,
    /// The persistent sync committee bitfield.
    #[superstruct(only(V2, V3))]
    pub syncnets: EnrSyncCommitteeBitfield<E>,
    #[superstruct(only(V3))]
    pub custody_group_count: u64,
}

impl<E: EthSpec> MetaData<E> {
    /// Returns a V1 MetaData response from self.
    pub fn metadata_v1(&self) -> Self {
        match self {
            md @ MetaData::V1(_) => md.clone(),
            MetaData::V2(metadata) => MetaData::V1(MetaDataV1 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
            }),
            MetaData::V3(metadata) => MetaData::V1(MetaDataV1 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
            }),
        }
    }

    /// Returns a V2 MetaData response from self by filling unavailable fields with default.
    pub fn metadata_v2(&self) -> Self {
        match self {
            MetaData::V1(metadata) => MetaData::V2(MetaDataV2 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
                syncnets: Default::default(),
            }),
            md @ MetaData::V2(_) => md.clone(),
            MetaData::V3(metadata) => MetaData::V2(MetaDataV2 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
                syncnets: metadata.syncnets.clone(),
            }),
        }
    }

    /// Returns a V3 MetaData response from self by filling unavailable fields with default.
    pub fn metadata_v3(&self, spec: &ChainSpec) -> Self {
        match self {
            MetaData::V1(metadata) => MetaData::V3(MetaDataV3 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
                syncnets: Default::default(),
                custody_group_count: spec.custody_requirement,
            }),
            MetaData::V2(metadata) => MetaData::V3(MetaDataV3 {
                seq_number: metadata.seq_number,
                attnets: metadata.attnets.clone(),
                syncnets: metadata.syncnets.clone(),
                custody_group_count: spec.custody_requirement,
            }),
            md @ MetaData::V3(_) => md.clone(),
        }
    }

    pub fn as_ssz_bytes(&self) -> Vec<u8> {
        match self {
            MetaData::V1(md) => md.as_ssz_bytes(),
            MetaData::V2(md) => md.as_ssz_bytes(),
            MetaData::V3(md) => md.as_ssz_bytes(),
        }
    }
}

/// The reason given for a `Goodbye` message.
///
/// Note: any unknown `u64::into(n)` will resolve to `Goodbye::Unknown` for any unknown `n`,
/// however `GoodbyeReason::Unknown.into()` will go into `0_u64`. Therefore de-serializing then
/// re-serializing may not return the same bytes.
#[derive(Debug, Clone, PartialEq)]
pub enum GoodbyeReason {
    /// This node has shutdown.
    ClientShutdown = 1,

    /// Incompatible networks.
    IrrelevantNetwork = 2,

    /// Error/fault in the RPC.
    Fault = 3,

    /// Teku uses this code for not being able to verify a network.
    UnableToVerifyNetwork = 128,

    /// The node has too many connected peers.
    TooManyPeers = 129,

    /// Scored poorly.
    BadScore = 250,

    /// The peer is banned
    Banned = 251,

    /// The IP address the peer is using is banned.
    BannedIP = 252,

    /// Unknown reason.
    Unknown = 0,
}

impl From<u64> for GoodbyeReason {
    fn from(id: u64) -> GoodbyeReason {
        match id {
            1 => GoodbyeReason::ClientShutdown,
            2 => GoodbyeReason::IrrelevantNetwork,
            3 => GoodbyeReason::Fault,
            128 => GoodbyeReason::UnableToVerifyNetwork,
            129 => GoodbyeReason::TooManyPeers,
            250 => GoodbyeReason::BadScore,
            251 => GoodbyeReason::Banned,
            252 => GoodbyeReason::BannedIP,
            _ => GoodbyeReason::Unknown,
        }
    }
}

impl From<GoodbyeReason> for u64 {
    fn from(reason: GoodbyeReason) -> u64 {
        reason as u64
    }
}

impl ssz::Encode for GoodbyeReason {
    fn is_ssz_fixed_len() -> bool {
        <u64 as ssz::Encode>::is_ssz_fixed_len()
    }

    fn ssz_fixed_len() -> usize {
        <u64 as ssz::Encode>::ssz_fixed_len()
    }

    fn ssz_bytes_len(&self) -> usize {
        0_u64.ssz_bytes_len()
    }

    fn ssz_append(&self, buf: &mut Vec<u8>) {
        let conv: u64 = self.clone().into();
        conv.ssz_append(buf)
    }
}

impl ssz::Decode for GoodbyeReason {
    fn is_ssz_fixed_len() -> bool {
        <u64 as ssz::Decode>::is_ssz_fixed_len()
    }

    fn ssz_fixed_len() -> usize {
        <u64 as ssz::Decode>::ssz_fixed_len()
    }

    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
        u64::from_ssz_bytes(bytes).map(|n| n.into())
    }
}

/// Request a number of beacon block roots from a peer.
#[superstruct(
    variants(V1, V2),
    variant_attributes(derive(Encode, Decode, Clone, Debug, PartialEq))
)]
#[derive(Clone, Debug, PartialEq)]
pub struct BlocksByRangeRequest {
    /// The starting slot to request blocks.
    pub start_slot: u64,

    /// The number of blocks from the start slot.
    pub count: u64,
}

impl BlocksByRangeRequest {
    /// The default request is V2
    pub fn new(start_slot: u64, count: u64) -> Self {
        Self::V2(BlocksByRangeRequestV2 { start_slot, count })
    }

    pub fn new_v1(start_slot: u64, count: u64) -> Self {
        Self::V1(BlocksByRangeRequestV1 { start_slot, count })
    }
}

/// Request a number of execution payload envelopes from a peer.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct PayloadEnvelopesByRangeRequest {
    /// The starting slot to request execution payload envelopes.
    pub start_slot: u64,

    /// The number of slots from the start slot.
    pub count: u64,
}

/// Request a number of beacon blobs from a peer.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct BlobsByRangeRequest {
    /// The starting slot to request blobs.
    pub start_slot: u64,

    /// The number of slots from the start slot.
    pub count: u64,
}

impl BlobsByRangeRequest {
    pub fn max_blobs_requested(&self, epoch: Epoch, spec: &ChainSpec) -> u64 {
        let max_blobs_per_block = spec.max_blobs_per_block(epoch);
        self.count.saturating_mul(max_blobs_per_block)
    }
}

/// Request a number of beacon data columns from a peer.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct DataColumnsByRangeRequest {
    /// The starting slot to request data columns.
    pub start_slot: u64,
    /// The number of slots from the start slot.
    pub count: u64,
    /// The list column indices being requested.
    pub columns: Vec<ColumnIndex>,
}

impl DataColumnsByRangeRequest {
    pub fn max_requested<E: EthSpec>(&self) -> u64 {
        self.count.saturating_mul(self.columns.len() as u64)
    }

    pub fn ssz_min_len() -> usize {
        DataColumnsByRangeRequest {
            start_slot: 0,
            count: 0,
            columns: vec![0],
        }
        .as_ssz_bytes()
        .len()
    }

    pub fn ssz_max_len<E: EthSpec>() -> usize {
        DataColumnsByRangeRequest {
            start_slot: 0,
            count: 0,
            columns: vec![0; E::number_of_columns()],
        }
        .as_ssz_bytes()
        .len()
    }
}

/// Request a number of beacon block roots from a peer.
#[superstruct(
    variants(V1, V2),
    variant_attributes(derive(Encode, Decode, Clone, Debug, PartialEq))
)]
#[derive(Clone, Debug, PartialEq)]
pub struct OldBlocksByRangeRequest {
    /// The starting slot to request blocks.
    pub start_slot: u64,

    /// The number of blocks from the start slot.
    pub count: u64,

    /// The step increment to receive blocks.
    ///
    /// A value of 1 returns every block.
    /// A value of 2 returns every second block.
    /// A value of 3 returns every third block and so on.
    pub step: u64,
}

impl OldBlocksByRangeRequest {
    /// The default request is V2
    pub fn new(start_slot: u64, count: u64, step: u64) -> Self {
        Self::V2(OldBlocksByRangeRequestV2 {
            start_slot,
            count,
            step,
        })
    }

    pub fn new_v1(start_slot: u64, count: u64, step: u64) -> Self {
        Self::V1(OldBlocksByRangeRequestV1 {
            start_slot,
            count,
            step,
        })
    }
}

impl From<BlocksByRangeRequest> for OldBlocksByRangeRequest {
    fn from(req: BlocksByRangeRequest) -> Self {
        match req {
            BlocksByRangeRequest::V1(ref req) => {
                OldBlocksByRangeRequest::V1(OldBlocksByRangeRequestV1 {
                    start_slot: req.start_slot,
                    count: req.count,
                    step: 1,
                })
            }
            BlocksByRangeRequest::V2(ref req) => {
                OldBlocksByRangeRequest::V2(OldBlocksByRangeRequestV2 {
                    start_slot: req.start_slot,
                    count: req.count,
                    step: 1,
                })
            }
        }
    }
}

/// Request a contiguous range of beacon blocks by walking the parent chain of `beacon_root`.
///
/// New in Fulu (see consensus-specs PR 5181). The responder walks the parent chain of
/// `beacon_root` (inclusive) and emits up to `count` blocks in descending slot order.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct BlocksByHeadRequest {
    /// The block root to start the parent walk from (inclusive).
    pub beacon_root: Hash256,
    /// The maximum number of blocks to return.
    pub count: u64,
}

/// Request a number of beacon block bodies from a peer.
#[superstruct(variants(V1, V2), variant_attributes(derive(Clone, Debug, PartialEq)))]
#[derive(Clone, Debug, PartialEq)]
pub struct BlocksByRootRequest {
    /// The list of beacon block bodies being requested.
    pub block_roots: RuntimeVariableList<Hash256>,
}

impl BlocksByRootRequest {
    pub fn new(block_roots: Vec<Hash256>, fork_context: &ForkContext) -> Result<Self, String> {
        let max_request_blocks = fork_context
            .spec
            .max_request_blocks(fork_context.current_fork_name());
        let block_roots = RuntimeVariableList::new(block_roots, max_request_blocks)
            .map_err(|e| format!("BlocksByRootRequestV2 too many roots: {e:?}"))?;
        Ok(Self::V2(BlocksByRootRequestV2 { block_roots }))
    }

    pub fn new_v1(block_roots: Vec<Hash256>, fork_context: &ForkContext) -> Result<Self, String> {
        let max_request_blocks = fork_context
            .spec
            .max_request_blocks(fork_context.current_fork_name());
        let block_roots = RuntimeVariableList::new(block_roots, max_request_blocks)
            .map_err(|e| format!("BlocksByRootRequestV1 too many roots: {e:?}"))?;
        Ok(Self::V1(BlocksByRootRequestV1 { block_roots }))
    }
}

/// Request a number of execution payload envelopes from a peer.
#[derive(Clone, Debug, PartialEq)]
pub struct PayloadEnvelopesByRootRequest {
    /// The list of beacon block roots used to request execution payload envelopes.
    pub beacon_block_roots: RuntimeVariableList<Hash256>,
}

impl PayloadEnvelopesByRootRequest {
    pub fn new(
        beacon_block_roots: Vec<Hash256>,
        fork_context: &ForkContext,
    ) -> Result<Self, String> {
        let max_requests_envelopes = fork_context.spec.max_request_payloads();

        let beacon_block_roots =
            RuntimeVariableList::new(beacon_block_roots, max_requests_envelopes).map_err(|e| {
                format!("ExecutionPayloadEnvelopesByRootRequest too many beacon block roots: {e:?}")
            })?;

        Ok(Self { beacon_block_roots })
    }
}

/// Request a number of beacon blocks and blobs from a peer.
#[derive(Clone, Debug, PartialEq)]
pub struct BlobsByRootRequest {
    /// The list of beacon block roots being requested.
    pub blob_ids: RuntimeVariableList<BlobIdentifier>,
}

impl BlobsByRootRequest {
    pub fn new(blob_ids: Vec<BlobIdentifier>, fork_context: &ForkContext) -> Result<Self, String> {
        let max_request_blob_sidecars = fork_context
            .spec
            .max_request_blob_sidecars(fork_context.current_fork_name());
        let blob_ids = RuntimeVariableList::new(blob_ids, max_request_blob_sidecars)
            .map_err(|e| format!("BlobsByRootRequestV1 too many blob IDs: {e:?}"))?;
        Ok(Self { blob_ids })
    }
}

/// Request a number of data columns from a peer.
#[derive(Clone, Debug, PartialEq)]
pub struct DataColumnsByRootRequest<E: EthSpec> {
    /// The list of beacon block roots and column indices being requested.
    pub data_column_ids: RuntimeVariableList<DataColumnsByRootIdentifier<E>>,
}

impl<E: EthSpec> DataColumnsByRootRequest<E> {
    pub fn new(
        data_column_ids: Vec<DataColumnsByRootIdentifier<E>>,
        max_request_blocks: usize,
    ) -> Result<Self, &'static str> {
        let data_column_ids = RuntimeVariableList::new(data_column_ids, max_request_blocks)
            .map_err(|_| "DataColumnsByRootRequest too many column IDs")?;
        Ok(Self { data_column_ids })
    }

    pub fn max_requested(&self) -> usize {
        self.data_column_ids.iter().map(|id| id.columns.len()).sum()
    }
}

/// Request a number of beacon data columns from a peer.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct LightClientUpdatesByRangeRequest {
    /// The starting period to request light client updates.
    pub start_period: u64,
    /// The number of periods from `start_period`.
    pub count: u64,
}

impl LightClientUpdatesByRangeRequest {
    pub fn max_requested(&self) -> u64 {
        MAX_REQUEST_LIGHT_CLIENT_UPDATES
    }

    pub fn ssz_min_len() -> usize {
        LightClientUpdatesByRangeRequest {
            start_period: 0,
            count: 0,
        }
        .as_ssz_bytes()
        .len()
    }

    pub fn ssz_max_len() -> usize {
        Self::ssz_min_len()
    }
}

/* RPC Handling and Grouping */
// Collection of enums and structs used by the Codecs to encode/decode RPC messages

#[derive(Debug, Clone, PartialEq)]
pub enum RpcSuccessResponse<E: EthSpec> {
    /// A HELLO message.
    Status(StatusMessage),

    /// A response to a get BLOCKS_BY_RANGE request. A None response signifies the end of the
    /// batch.
    BlocksByRange(Arc<SignedBeaconBlock<E>>),

    /// A response to a get BLOCKS_BY_ROOT request.
    BlocksByRoot(Arc<SignedBeaconBlock<E>>),

    /// A response to a get BEACON_BLOCKS_BY_HEAD request.
    BlocksByHead(Arc<SignedBeaconBlock<E>>),

    /// A response to a get EXECUTION_PAYLOAD_ENVELOPES_BY_RANGE request. A None response signifies
    /// the end of the batch.
    PayloadEnvelopesByRange(Arc<SignedExecutionPayloadEnvelope<E>>),

    /// A response to a get EXECUTION_PAYLOAD_ENVELOPES_BY_ROOT request.
    PayloadEnvelopesByRoot(Arc<SignedExecutionPayloadEnvelope<E>>),

    /// A response to a get BLOBS_BY_RANGE request
    BlobsByRange(Arc<BlobSidecar<E>>),

    /// A response to a get LIGHT_CLIENT_BOOTSTRAP request.
    LightClientBootstrap(Arc<LightClientBootstrap<E>>),

    /// A response to a get LIGHT_CLIENT_OPTIMISTIC_UPDATE request.
    LightClientOptimisticUpdate(Arc<LightClientOptimisticUpdate<E>>),

    /// A response to a get LIGHT_CLIENT_FINALITY_UPDATE request.
    LightClientFinalityUpdate(Arc<LightClientFinalityUpdate<E>>),

    /// A response to a get LIGHT_CLIENT_UPDATES_BY_RANGE request.
    LightClientUpdatesByRange(Arc<LightClientUpdate<E>>),

    /// A response to a get BLOBS_BY_ROOT request.
    BlobsByRoot(Arc<BlobSidecar<E>>),

    /// A response to a get DATA_COLUMN_SIDECARS_BY_ROOT request.
    DataColumnsByRoot(Arc<DataColumnSidecar<E>>),

    /// A response to a get DATA_COLUMN_SIDECARS_BY_RANGE request.
    DataColumnsByRange(Arc<DataColumnSidecar<E>>),

    /// A PONG response to a PING request.
    Pong(Ping),

    /// A response to a META_DATA request.
    MetaData(Arc<MetaData<E>>),
}

/// Indicates which response is being terminated by a stream termination response.
#[derive(Debug, Clone)]
pub enum ResponseTermination {
    /// Blocks by range stream termination.
    BlocksByRange,

    /// Blocks by root stream termination.
    BlocksByRoot,

    /// Blocks by head stream termination.
    BlocksByHead,

    /// Execution payload envelopes by range stream termination.
    PayloadEnvelopesByRange,

    /// Execution payload envelopes by root stream termination.
    PayloadEnvelopesByRoot,

    /// Blobs by range stream termination.
    BlobsByRange,

    /// Blobs by root stream termination.
    BlobsByRoot,

    /// Data column sidecars by root stream termination.
    DataColumnsByRoot,

    /// Data column sidecars by range stream termination.
    DataColumnsByRange,

    /// Light client updates by range stream termination.
    LightClientUpdatesByRange,
}

impl ResponseTermination {
    pub fn as_protocol(&self) -> Protocol {
        match self {
            ResponseTermination::BlocksByRange => Protocol::BlocksByRange,
            ResponseTermination::BlocksByRoot => Protocol::BlocksByRoot,
            ResponseTermination::BlocksByHead => Protocol::BlocksByHead,
            ResponseTermination::PayloadEnvelopesByRange => Protocol::PayloadEnvelopesByRange,
            ResponseTermination::PayloadEnvelopesByRoot => Protocol::PayloadEnvelopesByRoot,
            ResponseTermination::BlobsByRange => Protocol::BlobsByRange,
            ResponseTermination::BlobsByRoot => Protocol::BlobsByRoot,
            ResponseTermination::DataColumnsByRoot => Protocol::DataColumnsByRoot,
            ResponseTermination::DataColumnsByRange => Protocol::DataColumnsByRange,
            ResponseTermination::LightClientUpdatesByRange => Protocol::LightClientUpdatesByRange,
        }
    }
}

/// The structured response containing a result/code indicating success or failure
/// and the contents of the response
#[derive(Debug, Clone)]
pub enum RpcResponse<E: EthSpec> {
    /// The response is a successful.
    Success(RpcSuccessResponse<E>),

    Error(RpcErrorResponse, ErrorType),

    /// Received a stream termination indicating which response is being terminated.
    StreamTermination(ResponseTermination),
}

/// Request a light_client_bootstrap for light_clients peers.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct LightClientBootstrapRequest {
    pub root: Hash256,
}

/// The code assigned to an erroneous `RPCResponse`.
#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum RpcErrorResponse {
    RateLimited,
    BlobsNotFoundForBlock,
    InvalidRequest,
    ServerError,
    /// Error spec'd to indicate that a peer does not have blocks on a requested range.
    ResourceUnavailable,
    Unknown,
}

impl<E: EthSpec> RpcResponse<E> {
    /// Used to encode the response in the codec.
    pub fn as_u8(&self) -> Option<u8> {
        match self {
            RpcResponse::Success(_) => Some(0),
            RpcResponse::Error(code, _) => Some(code.as_u8()),
            RpcResponse::StreamTermination(_) => None,
        }
    }

    /// Tells the codec whether to decode as an RPCResponse or an error.
    pub fn is_response(response_code: u8) -> bool {
        matches!(response_code, 0)
    }

    /// Builds an RPCCodedResponse from a response code and an ErrorMessage
    pub fn from_error(response_code: u8, err: ErrorType) -> Self {
        let code = match response_code {
            1 => RpcErrorResponse::InvalidRequest,
            2 => RpcErrorResponse::ServerError,
            3 => RpcErrorResponse::ResourceUnavailable,
            139 => RpcErrorResponse::RateLimited,
            140 => RpcErrorResponse::BlobsNotFoundForBlock,
            _ => RpcErrorResponse::Unknown,
        };
        RpcResponse::Error(code, err)
    }

    /// Returns true if this response always terminates the stream.
    pub fn close_after(&self) -> bool {
        !matches!(self, RpcResponse::Success(_))
    }
}

impl RpcErrorResponse {
    fn as_u8(&self) -> u8 {
        match self {
            RpcErrorResponse::InvalidRequest => 1,
            RpcErrorResponse::ServerError => 2,
            RpcErrorResponse::ResourceUnavailable => 3,
            RpcErrorResponse::Unknown => 255,
            RpcErrorResponse::RateLimited => 139,
            RpcErrorResponse::BlobsNotFoundForBlock => 140,
        }
    }
}

use super::Protocol;
impl<E: EthSpec> RpcSuccessResponse<E> {
    pub fn protocol(&self) -> Protocol {
        match self {
            RpcSuccessResponse::Status(_) => Protocol::Status,
            RpcSuccessResponse::BlocksByRange(_) => Protocol::BlocksByRange,
            RpcSuccessResponse::BlocksByRoot(_) => Protocol::BlocksByRoot,
            RpcSuccessResponse::BlocksByHead(_) => Protocol::BlocksByHead,
            RpcSuccessResponse::PayloadEnvelopesByRange(_) => Protocol::PayloadEnvelopesByRange,
            RpcSuccessResponse::PayloadEnvelopesByRoot(_) => Protocol::PayloadEnvelopesByRoot,
            RpcSuccessResponse::BlobsByRange(_) => Protocol::BlobsByRange,
            RpcSuccessResponse::BlobsByRoot(_) => Protocol::BlobsByRoot,
            RpcSuccessResponse::DataColumnsByRoot(_) => Protocol::DataColumnsByRoot,
            RpcSuccessResponse::DataColumnsByRange(_) => Protocol::DataColumnsByRange,
            RpcSuccessResponse::Pong(_) => Protocol::Ping,
            RpcSuccessResponse::MetaData(_) => Protocol::MetaData,
            RpcSuccessResponse::LightClientBootstrap(_) => Protocol::LightClientBootstrap,
            RpcSuccessResponse::LightClientOptimisticUpdate(_) => {
                Protocol::LightClientOptimisticUpdate
            }
            RpcSuccessResponse::LightClientFinalityUpdate(_) => Protocol::LightClientFinalityUpdate,
            RpcSuccessResponse::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange,
        }
    }

    pub fn slot(&self) -> Option<Slot> {
        match self {
            Self::BlocksByRange(r) | Self::BlocksByRoot(r) | Self::BlocksByHead(r) => {
                Some(r.slot())
            }
            Self::PayloadEnvelopesByRoot(r) | Self::PayloadEnvelopesByRange(r) => Some(r.slot()),
            Self::BlobsByRange(r) | Self::BlobsByRoot(r) => Some(r.slot()),
            Self::DataColumnsByRange(r) | Self::DataColumnsByRoot(r) => Some(r.slot()),
            Self::LightClientBootstrap(r) => Some(r.get_slot()),
            Self::LightClientFinalityUpdate(r) => Some(r.get_attested_header_slot()),
            Self::LightClientOptimisticUpdate(r) => Some(r.get_slot()),
            Self::LightClientUpdatesByRange(r) => Some(r.attested_header_slot()),
            Self::MetaData(_) | Self::Status(_) | Self::Pong(_) => None,
        }
    }
}

impl std::fmt::Display for RpcErrorResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let repr = match self {
            RpcErrorResponse::InvalidRequest => "The request was invalid",
            RpcErrorResponse::ResourceUnavailable => "Resource unavailable",
            RpcErrorResponse::ServerError => "Server error occurred",
            RpcErrorResponse::Unknown => "Unknown error occurred",
            RpcErrorResponse::RateLimited => "Rate limited",
            RpcErrorResponse::BlobsNotFoundForBlock => "No blobs for the given root",
        };
        f.write_str(repr)
    }
}

impl std::fmt::Display for StatusMessage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Status Message: Fork Digest: {:?}, Finalized Root: {}, Finalized Epoch: {}, Head Root: {}, Head Slot: {}, Earliest available slot: {:?}",
            self.fork_digest(),
            self.finalized_root(),
            self.finalized_epoch(),
            self.head_root(),
            self.head_slot(),
            self.earliest_available_slot()
        )
    }
}

impl<E: EthSpec> std::fmt::Display for RpcSuccessResponse<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RpcSuccessResponse::Status(status) => write!(f, "{}", status),
            RpcSuccessResponse::BlocksByRange(block) => {
                write!(f, "BlocksByRange: Block slot: {}", block.slot())
            }
            RpcSuccessResponse::BlocksByRoot(block) => {
                write!(f, "BlocksByRoot: Block slot: {}", block.slot())
            }
            RpcSuccessResponse::BlocksByHead(block) => {
                write!(f, "BlocksByHead: Block slot: {}", block.slot())
            }
            RpcSuccessResponse::PayloadEnvelopesByRange(envelope) => {
                write!(
                    f,
                    "ExecutionPayloadEnvelopesByRange: Envelope slot: {}",
                    envelope.slot()
                )
            }
            RpcSuccessResponse::PayloadEnvelopesByRoot(envelope) => {
                write!(
                    f,
                    "ExecutionPayloadEnvelopesByRoot: Envelope slot: {}",
                    envelope.slot()
                )
            }
            RpcSuccessResponse::BlobsByRange(blob) => {
                write!(f, "BlobsByRange: Blob slot: {}", blob.slot())
            }
            RpcSuccessResponse::BlobsByRoot(sidecar) => {
                write!(f, "BlobsByRoot: Blob slot: {}", sidecar.slot())
            }
            RpcSuccessResponse::DataColumnsByRoot(sidecar) => {
                write!(f, "DataColumnsByRoot: Data column slot: {}", sidecar.slot())
            }
            RpcSuccessResponse::DataColumnsByRange(sidecar) => {
                write!(
                    f,
                    "DataColumnsByRange: Data column slot: {}",
                    sidecar.slot()
                )
            }
            RpcSuccessResponse::Pong(ping) => write!(f, "Pong: {}", ping.data),
            RpcSuccessResponse::MetaData(metadata) => {
                write!(f, "Metadata: {}", metadata.seq_number())
            }
            RpcSuccessResponse::LightClientBootstrap(bootstrap) => {
                write!(f, "LightClientBootstrap Slot: {}", bootstrap.get_slot())
            }
            RpcSuccessResponse::LightClientOptimisticUpdate(update) => {
                write!(
                    f,
                    "LightClientOptimisticUpdate Slot: {}",
                    update.signature_slot()
                )
            }
            RpcSuccessResponse::LightClientFinalityUpdate(update) => {
                write!(
                    f,
                    "LightClientFinalityUpdate Slot: {}",
                    update.signature_slot()
                )
            }
            RpcSuccessResponse::LightClientUpdatesByRange(update) => {
                write!(
                    f,
                    "LightClientUpdatesByRange Slot: {}",
                    update.signature_slot(),
                )
            }
        }
    }
}

impl<E: EthSpec> std::fmt::Display for RpcResponse<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RpcResponse::Success(res) => write!(f, "{}", res),
            RpcResponse::Error(code, err) => write!(f, "{}: {}", code, err),
            RpcResponse::StreamTermination(_) => write!(f, "Stream Termination"),
        }
    }
}

impl std::fmt::Display for GoodbyeReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GoodbyeReason::ClientShutdown => write!(f, "Client Shutdown"),
            GoodbyeReason::IrrelevantNetwork => write!(f, "Irrelevant Network"),
            GoodbyeReason::Fault => write!(f, "Fault"),
            GoodbyeReason::UnableToVerifyNetwork => write!(f, "Unable to verify network"),
            GoodbyeReason::TooManyPeers => write!(f, "Too many peers"),
            GoodbyeReason::BadScore => write!(f, "Bad Score"),
            GoodbyeReason::Banned => write!(f, "Banned"),
            GoodbyeReason::BannedIP => write!(f, "BannedIP"),
            GoodbyeReason::Unknown => write!(f, "Unknown Reason"),
        }
    }
}

impl std::fmt::Display for BlocksByRangeRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Start Slot: {}, Count: {}",
            self.start_slot(),
            self.count()
        )
    }
}

impl std::fmt::Display for OldBlocksByRangeRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Start Slot: {}, Count: {}, Step: {}",
            self.start_slot(),
            self.count(),
            self.step()
        )
    }
}

impl std::fmt::Display for BlocksByHeadRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "BlocksByHead: beacon_root: {}, count: {}",
            self.beacon_root, self.count
        )
    }
}

impl std::fmt::Display for BlobsByRootRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Request: BlobsByRoot: Number of Requested Roots: {}",
            self.blob_ids.len()
        )
    }
}

impl std::fmt::Display for BlobsByRangeRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Request: BlobsByRange: Start Slot: {}, Count: {}",
            self.start_slot, self.count
        )
    }
}

impl<E: EthSpec> std::fmt::Display for DataColumnsByRootRequest<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Request: DataColumnsByRoot: Number of Requested Data Column Ids: {}",
            self.data_column_ids.len()
        )
    }
}
