// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use crate::block_processor::pruning::{
    EVERYTHING_PRUNING_INTERVAL, EVERYTHING_PRUNING_KEEP_RECENT,
};
use crate::helpers::MalformedDataError;
use crate::storage::NyxdScraperStorageError;
use tendermint::Hash;
use thiserror::Error;
use tokio::sync::mpsc::error::SendError;

#[derive(Debug, Error)]
pub enum ScraperError {
    #[error("storage error: {0}")]
    StorageError(#[from] NyxdScraperStorageError),

    #[error("the block scraper is already running")]
    ScraperAlreadyRunning,

    #[error("block information for height {height} is not available on the provided rpc endpoint")]
    BlocksUnavailable { height: u32 },

    #[error("failed to establish websocket connection to {url}: {source}")]
    WebSocketConnectionFailure {
        url: String,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("failed to establish rpc connection to {url}: {source}")]
    HttpConnectionFailure {
        url: String,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("failed to create chain subscription: {source}")]
    ChainSubscriptionFailure {
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not obtain basic block information at height: {height}: {source}")]
    BlockQueryFailure {
        height: u32,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not obtain block results information at height: {height}: {source}")]
    BlockResultsQueryFailure {
        height: u32,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not obtain validators information at height: {height}: {source}")]
    ValidatorsQueryFailure {
        height: u32,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not obtain tx results for tx: {hash}: {source}")]
    TxResultsQueryFailure {
        hash: Hash,
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not obtain current abci info: {source}")]
    AbciInfoQueryFailure {
        #[source]
        source: Box<tendermint_rpc::Error>,
    },

    #[error("could not parse tx {hash}: {source}")]
    TxParseFailure {
        hash: Hash,
        #[source]
        source: cosmrs::ErrorReport,
    },

    #[error("could not parse msg of type {type_url}: {source}")]
    MsgParseFailure {
        type_url: String,
        #[source]
        source: cosmrs::ErrorReport,
    },

    #[error(
        "received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')"
    )]
    InvalidSubscriptionEvent { query: String, kind: String },

    #[error("received block data was empty (query: '{query}')")]
    EmptyBlockData { query: String },

    #[error("reached maximum number of allowed errors for subscription events")]
    MaximumWebSocketFailures,

    #[error("failed to begin storage tx: {source}")]
    StorageTxBeginFailure {
        #[source]
        source: NyxdScraperStorageError,
    },

    #[error("failed to commit storage tx: {source}")]
    StorageTxCommitFailure {
        #[source]
        source: NyxdScraperStorageError,
    },

    #[error("failed to send on a closed channel")]
    ClosedChannelError,

    #[error(transparent)]
    MalformedData(#[from] MalformedDataError),

    #[error(
        "could not find the block proposer ('{proposer}') for height {height} in the validator set"
    )]
    BlockProposerNotInValidatorSet { height: u32, proposer: String },

    #[error(
        "pruning.interval must not be set to 0. If you want to disable pruning, select pruning.strategy = \"nothing\""
    )]
    ZeroPruningInterval,

    #[error(
        "pruning.interval must not be smaller than {}. got: {interval}. for most aggressive pruning, select pruning.strategy = \"everything\"",
        EVERYTHING_PRUNING_INTERVAL
    )]
    TooSmallPruningInterval { interval: u32 },

    #[error(
        "pruning.keep_recent must not be smaller than {}. got: {keep_recent}. for most aggressive pruning, select pruning.strategy = \"everything\"",
        EVERYTHING_PRUNING_KEEP_RECENT
    )]
    TooSmallKeepRecent { keep_recent: u32 },

    #[error("'{type_url}' is not registered in the message registry")]
    MissingTypeUrlRegistration { type_url: String },

    #[error("failed to decode message of type '{type_url}': {error}")]
    InvalidProtoRepresentation {
        type_url: String,
        #[source]
        error: prost::DecodeError,
    },

    #[error("failed to encode message of type '{type_url}' to json: '{error}'")]
    JsonSerialisationFailure {
        type_url: String,
        #[source]
        error: serde_json::Error,
    },

    #[error("serialisation of message of type '{type_url}' didn't result in an object!")]
    JsonSerialisationFailureNotObject { type_url: String },

    #[error("field '{field}' in '{type_url}' is not a string")]
    JsonWasmSerialisationFailureNotString { field: String, type_url: String },

    #[error("field '{field}' in '{type_url}' has invalid base64 encoding: {error}")]
    JsonWasmSerialisationFailureInvalidBase64Encoding {
        field: String,
        type_url: String,
        #[source]
        error: base64::DecodeError,
    },

    #[error("no modules configured for the chain watcher")]
    NoModulesConfigured,

    #[error("no storage configured for the chain watcher")]
    NoStorageConfigured,
}

impl ScraperError {
    pub fn tx_begin_failure(source: NyxdScraperStorageError) -> ScraperError {
        ScraperError::StorageTxBeginFailure { source }
    }

    pub fn tx_commit_failure(source: NyxdScraperStorageError) -> ScraperError {
        ScraperError::StorageTxCommitFailure { source }
    }
}

impl<T> From<SendError<T>> for ScraperError {
    fn from(_: SendError<T>) -> Self {
        ScraperError::ClosedChannelError
    }
}
