use affinidi_secrets_resolver::errors::SecretsResolverError;
use serde::Serialize;
use serde_json::error::Category;
use std::fmt;

#[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
pub enum ErrorKind {
    #[error("DID not resolved")]
    DIDNotResolved,

    #[error("DID URL not found")]
    DIDUrlNotFound,

    #[error("Secret not found")]
    SecretNotFound,

    #[error("Malformed")]
    Malformed,

    #[error("IO error")]
    IoError,

    #[error("Invalid state")]
    InvalidState,

    #[error("No compatible crypto")]
    NoCompatibleCrypto,

    #[error("Unsupported crypto or method")]
    Unsupported,

    #[error("Illegal argument")]
    IllegalArgument,

    #[error("Too many crypto operations")]
    TooManyCryptoOperations,

    #[error("Secrets Resolver error")]
    SecretsResolverError,
}

#[derive(Debug, thiserror::Error)]
#[error("{kind}: {source:#}")]
pub struct Error {
    kind: ErrorKind,
    pub source: anyhow::Error,
}

impl Error {
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }

    pub fn new<E>(kind: ErrorKind, source: E) -> Error
    where
        E: std::error::Error + Send + Sync + 'static,
    {
        Error {
            kind,
            source: anyhow::Error::new(source),
        }
    }

    pub fn msg<D>(kind: ErrorKind, msg: D) -> Error
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        Error {
            kind,
            source: anyhow::Error::msg(msg),
        }
    }
}

pub type Result<T> = std::result::Result<T, Error>;

pub trait ResultExt<T, E> {
    fn kind<D>(self, kind: ErrorKind, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static;
}

impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn kind<D>(self, kind: ErrorKind, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        self.map_err(|e| Error {
            kind,
            source: anyhow::Error::new(e).context(msg),
        })
    }
}

pub trait ResultExtNoContext<T, E> {
    fn to_error_kind(self, kind: ErrorKind) -> std::result::Result<T, ErrorKind>;

    fn kind_no_context<D>(self, kind: ErrorKind, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static;
}

impl<T, E> ResultExtNoContext<T, E> for std::result::Result<T, E> {
    fn to_error_kind(self, kind: ErrorKind) -> std::result::Result<T, ErrorKind> {
        self.map_err(|_| kind)
    }

    fn kind_no_context<D>(self, kind: ErrorKind, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        self.map_err(|_| Error::msg(kind, msg))
    }
}

pub trait ResultContext<T> {
    fn context<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static;
}

impl<T> ResultContext<T> for Result<T> {
    fn context<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        self.map_err(|e| {
            let Error { kind, source } = e;

            Error {
                kind,
                source: source.context(msg),
            }
        })
    }
}

pub trait ToResult<T> {
    fn to_didcomm<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static;
}

impl<T> ToResult<T> for serde_json::Result<T> {
    fn to_didcomm<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        ResultContext::context(self.map_err(|e| e.into()), msg)
    }
}

impl<T> ToResult<T> for bs58::decode::Result<T> {
    fn to_didcomm<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        ResultContext::context(self.map_err(|e| e.into()), msg)
    }
}

impl<T> ToResult<T> for bs58::encode::Result<T> {
    fn to_didcomm<D>(self, msg: D) -> Result<T>
    where
        D: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        ResultContext::context(self.map_err(|e| e.into()), msg)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        match err.classify() {
            Category::Io | Category::Eof => Error::msg(ErrorKind::InvalidState, err.to_string()),
            _ => Error::msg(ErrorKind::Malformed, err.to_string()),
        }
    }
}

impl From<bs58::decode::Error> for Error {
    fn from(err: bs58::decode::Error) -> Self {
        match err {
            bs58::decode::Error::BufferTooSmall => {
                Error::msg(ErrorKind::InvalidState, err.to_string())
            }
            _ => Error::msg(ErrorKind::Malformed, err.to_string()),
        }
    }
}

impl From<bs58::encode::Error> for Error {
    fn from(err: bs58::encode::Error) -> Self {
        Error::msg(ErrorKind::InvalidState, err.to_string())
    }
}

impl From<SecretsResolverError> for Error {
    fn from(err: SecretsResolverError) -> Self {
        Error::msg(ErrorKind::SecretsResolverError, err.to_string())
    }
}

pub fn err_msg<D>(kind: ErrorKind, msg: D) -> Error
where
    D: fmt::Display + fmt::Debug + Send + Sync + 'static,
{
    Error::msg(kind, msg)
}
