use std::borrow::Cow;
use std::sync::Arc;

use thiserror::Error;
use tonlib_core::cell::TonCellError;
use tonlib_core::library_helper::TonLibraryError;
use tonlib_core::TonAddress;

use crate::client::TonClientError;
use crate::emulator::error::TvmEmulatorError;
use crate::tl::TvmStackError;
use crate::types::{StackParseError, TonMethodId, TvmStackEntry};

#[derive(Error, Debug)]
#[allow(clippy::result_large_err)]
pub enum TonContractError {
    #[error("Cell error (Method: {method}, address: {address}, error: {error})")]
    CellError {
        method: String,
        address: TonAddress,
        error: TonCellError,
    },
    #[error("TonClientError ({0})")]
    ClientError(#[from] TonClientError),

    #[error("Method emulation error (Method: {method}, address: {address}, error {error}")]
    MethodEmulationError {
        method: String,
        address: TonAddress,
        error: TvmEmulatorError,
    },

    #[error("Message emulation error (address: {address}, error {error}")]
    MessageEmulationError {
        address: TonAddress,
        error: TvmEmulatorError,
    },

    #[error("Invalid argument ({0})")]
    InvalidArgument(String),

    #[error("Internal error ({0})")]
    InternalError(String),

    #[error("Invalid method result stack size (Method: {method}, address: {address}, actual: {actual}, expected: {expected})")]
    InvalidMethodResultStackSize {
        method: String,
        address: TonAddress,
        actual: usize,
        expected: usize,
    },

    #[error(
        "Method result stack error (Method: {method}, address: {address}, stack error: {error:?})"
    )]
    MethodResultStackError {
        method: TonMethodId,
        address: TonAddress,
        error: TvmStackError,
    },

    #[error("Missing library (Method: {method}, address: {address}, lib: {missing_library})")]
    MissingLibrary {
        method: TonMethodId,
        address: TonAddress,
        missing_library: String,
    },

    #[error("Too many libraries required (Method: {method}, address: {address})")]
    TooManyLibraries {
        limit: usize,
        method: TonMethodId,
        address: TonAddress,
    },

    #[error("Library not found (Address: {address}, lib: {missing_library})")]
    LibraryNotFound {
        address: TonAddress,
        missing_library: String,
    },

    #[error(
        "Tvm stack parse  error (Method: {method}, address: {address}, stack error: {error:?})"
    )]
    #[allow(clippy::result_large_err)]
    TvmStackParseError {
        method: TonMethodId,
        address: TonAddress,
        error: Box<StackParseError>,
    },

    #[error(
        "Tvm run error (Method: {method}, address: {address}, exit code: {exit_code}, gas used: {gas_used}, stack: {stack:?}, vm_log: {vm_log:?}, missing_library: {missing_library:?})"
    )]
    #[allow(clippy::result_large_err)]
    TvmRunError {
        method: TonMethodId,
        address: TonAddress,
        vm_log: Box<Option<String>>,
        exit_code: i32,
        stack: Box<Vec<TvmStackEntry>>,
        missing_library: Option<String>,
        gas_used: i64,
    },

    #[error("{0}")]
    CacheError(#[from] Arc<TonContractError>),

    #[error("{0}")]
    TonLibraryError(#[from] TonLibraryError),
}

pub trait MapStackError<R> {
    #[allow(clippy::result_large_err)]
    fn map_stack_error(
        self,
        method: impl Into<Cow<'static, str>>,
        address: &TonAddress,
    ) -> Result<R, TonContractError>;
}

pub trait MapCellError<R> {
    #[allow(clippy::result_large_err)]
    fn map_cell_error(
        self,
        method: impl Into<Cow<'static, str>>,
        address: &TonAddress,
    ) -> Result<R, TonContractError>;
}

impl<R> MapStackError<R> for Result<R, TvmStackError> {
    fn map_stack_error(
        self,
        method: impl Into<Cow<'static, str>>,
        address: &TonAddress,
    ) -> Result<R, TonContractError> {
        self.map_err(
            |e: TvmStackError| TonContractError::MethodResultStackError {
                method: TonMethodId::Name(method.into()),
                address: address.clone(),
                error: e,
            },
        )
    }
}

impl<R> MapStackError<R> for Result<R, StackParseError> {
    fn map_stack_error(
        self,
        method: impl Into<Cow<'static, str>>,
        address: &TonAddress,
    ) -> Result<R, TonContractError> {
        self.map_err(|e| TonContractError::TvmStackParseError {
            method: TonMethodId::Name(method.into()),
            address: address.clone(),
            error: e.into(),
        })
    }
}

impl<R> MapCellError<R> for Result<R, TonCellError> {
    fn map_cell_error(
        self,
        method: impl Into<Cow<'static, str>>,
        address: &TonAddress,
    ) -> Result<R, TonContractError> {
        self.map_err(|e| TonContractError::MethodResultStackError {
            method: TonMethodId::Name(method.into()),
            address: address.clone(),
            error: e.into(),
        })
    }
}
