//! Error handling
//!
//! Handles all kinds of errors generated by either iota
//! wallet or iota client or the sdk itself
//!
//!

use crate::{
    backend::error::ApiError, core::viviswap::ViviswapError, types::error::TypeError, user::error::UserKvStorageError,
    wallet::error::WalletError,
};
use serde::{ser::Serializer, Serialize};
use std::fmt::Display;

/// A [`core::result::Result`] with [`Error`] as its error variant.
pub type Result<T> = core::result::Result<T, Error>;

/// Wrapper type for all SDK errors.
#[derive(thiserror::Error, Serialize)]
#[serde(tag = "type", content = "error", rename_all = "camelCase")]
pub enum Error {
    /// Error occurs if the config is missing
    #[error("No config is set. Please use the set_config function.")]
    MissingConfig,

    /// Error occurs if the network is missing
    #[error("No network is set. Please use the set_network function.")]
    MissingNetwork,

    /// Error occurs if the network is unavailable
    #[error("Network with id {0} is unavailable.")]
    NetworkUnavailable(String),

    /// Error occurs if the chain id for evm network is missing
    #[error("Chain ID cannot be empty for EVM network type.")]
    EmptyChainIdForEvm,

    /// Error occurs when the config is not initialized or there are missing fields
    #[error("Error while setting the configuration: {0}")]
    SetConfig(String),

    /// Error occurs when user is not initialized
    #[error("User is not initialized")]
    UserNotInitialized,

    /// Error occurs when the user repository is not initialized
    #[error("User repositry is not initialized")]
    UserRepoNotInitialized,

    /// Error caused by conversions to/from Decimal and f64
    #[error("Decimal error: {0}")]
    #[serde(serialize_with = "display_string")]
    Decimal(rust_decimal::Error),

    /// User repository error
    #[error("User repository error: {0}")]
    #[serde(serialize_with = "display_string")]
    UserRepository(#[from] UserKvStorageError),

    /// Error raises if user is already kyc verified
    #[error("User is already kyc verified")]
    UserAlreadyKycVerified,

    /// Error raises if authentication token is outdated or invalid
    #[error("Unauthorized: Missing Access Token")]
    MissingAccessToken,

    /// Error occurs if chain id is not defined in configuration
    #[error("Chain id is not defined")]
    ChainIdNotDefined,

    /// Error raises if chain_id cannot be converted
    #[error("ParseChainIdError: {0}")]
    ParseChainIdError(String),

    /// Error raises if something failed to parse
    #[error("Parse error: {0}")]
    Parse(String),

    /// User provided a negative invalid amount to send or create a purchase request with
    #[error("NegativeAmount")]
    NegativeAmount,

    /// Error from fern logger
    #[cfg(not(target_arch = "wasm32"))]
    #[serde(serialize_with = "display_string")]
    #[error("LoggerInit error: {0}")]
    LoggerInit(fern_logger::Error),

    /// Error occurs in sdk backend (api)
    #[error("BackendApi error: {0}")]
    #[serde(serialize_with = "debug_string")]
    BackendApi(#[from] ApiError),

    /// Error occurs in sdk types
    #[error("Type error: {0}")]
    #[serde(serialize_with = "debug_string")]
    Type(#[from] TypeError),

    /// Error occurs in sdk wallet
    #[error("Wallet error: {0}")]
    #[serde(serialize_with = "debug_string")]
    Wallet(#[from] WalletError),

    /// Viviswap related errors
    #[error("Viviswap error: {0}")]
    #[serde(serialize_with = "debug_string")]
    Viviswap(ViviswapError),
}

impl From<rust_decimal::Error> for Error {
    fn from(value: rust_decimal::Error) -> Self {
        Self::Decimal(value)
    }
}

impl std::fmt::Debug for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        error_chain_fmt(self, f)
    }
}

/// Writes an entire error chain to the provided formatter
fn error_chain_fmt(e: &impl std::error::Error, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    writeln!(f, "{}\n", e)?;
    let mut current = e.source();
    while let Some(cause) = current {
        writeln!(f, "Caused by:\n\t{}", cause)?;
        current = cause.source();
    }
    Ok(())
}

/// Use this to serialize Error variants that implements Debug but not Serialize
fn display_string<T, S>(value: &T, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
    T: Display,
    S: Serializer,
{
    value.to_string().serialize(serializer)
}

fn debug_string<T, S>(value: &T, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
    T: std::fmt::Debug,
    S: Serializer,
{
    format!("{:?}", value).serialize(serializer)
}
