pub mod axum_error;

use crate::prelude::StringExt;
use http::StatusCode;
use mongodb::error::WriteFailure;
use serde::Serialize;
use serde_json::{json, Value};
use std::convert::AsRef;
use std::{
    error::Error as StdError,
    fmt::{Debug, Display, Formatter, Result as FmtResult},
    sync::Arc,
};
use strum::AsRefStr;
use thiserror::Error as ThisError;

pub trait ErrorMeta {
    fn code(&self) -> ErrorCode;
    fn key(&self) -> ErrorKey;
    fn message(&self) -> ErrorMessage;
    fn meta(&self) -> Option<Value>;
}

#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize)]
pub struct ErrorCode(u16);

impl ErrorCode {
    pub fn as_u16(&self) -> u16 {
        self.0
    }
}

impl Display for ErrorCode {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize)]
pub struct ErrorKey(String);

impl ErrorKey {
    pub fn internal(key: &str, subtype: Option<&str>) -> Self {
        if let Some(subtype) = subtype {
            ErrorKey(format!("err::internal::{}::{}", key, subtype))
        } else {
            ErrorKey(format!("err::internal::{}", key))
        }
    }

    pub fn application(key: &str, subtype: Option<&str>) -> Self {
        if let Some(subtype) = subtype {
            ErrorKey(format!("err::application::{}::{}", key, subtype))
        } else {
            ErrorKey(format!("err::application::{}", key))
        }
    }
}

impl Display for ErrorKey {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize)]
pub struct ErrorMessage(String);

impl AsRef<str> for ErrorMessage {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl Display for ErrorMessage {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{}", self.0)
    }
}

#[derive(ThisError, Clone, Eq, PartialEq, Serialize, AsRefStr)]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "PascalCase")]
pub enum InternalError {
    #[error("An unknown error occurred: {}", .message)]
    UnknownError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("A unique field violation occurred: {}", .message)]
    UniqueFieldViolation {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("A timeout occurred: {}", .message)]
    Timeout {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("A connection error occurred: {}", .message)]
    ConnectionError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Entity not found: {}", .message)]
    KeyNotFound {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Argument provided is invalid: {}", .message)]
    InvalidArgument {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("An error while performing an IO operation: {}", .message)]
    IOErr {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Encription error: {}", .message)]
    EncryptionError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Decryption error: {}", .message)]
    DecryptionError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Configuration error: {}", .message)]
    ConfigurationError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Serialization error: {}", .message)]
    SerializeError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Deserialization error: {}", .message)]
    DeserializeError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("An error occurred running the javascript function: {}", .message)]
    ScriptError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
}

impl From<anyhow::Error> for InternalError {
    fn from(error: anyhow::Error) -> Self {
        match error.downcast_ref::<InternalError>() {
            Some(integration_error) => integration_error.clone(),
            None => InternalError::UnknownError {
                message: error.to_string(),
                subtype: None,
                meta: None,
            },
        }
    }
}

impl InternalError {
    pub fn unknown(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::UnknownError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn unique_field_violation(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::UniqueFieldViolation {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn timeout(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::Timeout {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn script_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::ScriptError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn serialize_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::SerializeError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn deserialize_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::DeserializeError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn configuration_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::ConfigurationError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn encryption_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::EncryptionError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn decryption_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::DecryptionError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn connection_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::ConnectionError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn io_err(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::IOErr {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn key_not_found(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::KeyNotFound {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn invalid_argument(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::internal(InternalError::InvalidArgument {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    fn set_meta(self, metadata: Value) -> Self {
        match self {
            InternalError::UnknownError {
                message, subtype, ..
            } => InternalError::UnknownError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::UniqueFieldViolation {
                message, subtype, ..
            } => InternalError::UniqueFieldViolation {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::Timeout {
                message, subtype, ..
            } => InternalError::Timeout {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::ConnectionError {
                message, subtype, ..
            } => InternalError::ConnectionError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::KeyNotFound {
                message, subtype, ..
            } => InternalError::KeyNotFound {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::InvalidArgument {
                message, subtype, ..
            } => InternalError::InvalidArgument {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::IOErr {
                message, subtype, ..
            } => InternalError::IOErr {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::EncryptionError {
                message, subtype, ..
            } => InternalError::EncryptionError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::DecryptionError {
                message, subtype, ..
            } => InternalError::DecryptionError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::ConfigurationError {
                message, subtype, ..
            } => InternalError::ConfigurationError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::ScriptError {
                message, subtype, ..
            } => InternalError::ScriptError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::SerializeError {
                message, subtype, ..
            } => InternalError::SerializeError {
                message,
                subtype,
                meta: Some(metadata),
            },
            InternalError::DeserializeError {
                message, subtype, ..
            } => InternalError::DeserializeError {
                message,
                subtype,
                meta: Some(metadata),
            },
        }
    }
}

impl ErrorMeta for InternalError {
    fn code(&self) -> ErrorCode {
        match self {
            InternalError::UnknownError { .. } => ErrorCode(1000),
            InternalError::UniqueFieldViolation { .. } => ErrorCode(1001),
            InternalError::Timeout { .. } => ErrorCode(1002),
            InternalError::ConnectionError { .. } => ErrorCode(1003),
            InternalError::KeyNotFound { .. } => ErrorCode(1004),
            InternalError::InvalidArgument { .. } => ErrorCode(1005),
            InternalError::IOErr { .. } => ErrorCode(1006),
            InternalError::EncryptionError { .. } => ErrorCode(1007),
            InternalError::DecryptionError { .. } => ErrorCode(1008),
            InternalError::ConfigurationError { .. } => ErrorCode(1009),
            InternalError::ScriptError { .. } => ErrorCode(1010),
            InternalError::SerializeError { .. } => ErrorCode(1011),
            InternalError::DeserializeError { .. } => ErrorCode(1012),
        }
    }

    fn key(&self) -> ErrorKey {
        match self {
            InternalError::UnknownError { subtype, .. } => {
                ErrorKey::internal("unknown", subtype.as_deref())
            }
            InternalError::UniqueFieldViolation { subtype, .. } => {
                ErrorKey::internal("unique_violation", subtype.as_deref())
            }
            InternalError::Timeout { subtype, .. } => {
                ErrorKey::internal("timeout", subtype.as_deref())
            }
            InternalError::ConnectionError { subtype, .. } => {
                ErrorKey::internal("connection_error", subtype.as_deref())
            }
            InternalError::KeyNotFound { subtype, .. } => {
                ErrorKey::internal("key_not_found", subtype.as_deref())
            }
            InternalError::InvalidArgument { subtype, .. } => {
                ErrorKey::internal("invalid_argument", subtype.as_deref())
            }
            InternalError::IOErr { subtype, .. } => {
                ErrorKey::internal("io_err", subtype.as_deref())
            }
            InternalError::EncryptionError { subtype, .. } => {
                ErrorKey::internal("encryption_error", subtype.as_deref())
            }
            InternalError::DecryptionError { subtype, .. } => {
                ErrorKey::internal("decryption_error", subtype.as_deref())
            }
            InternalError::ConfigurationError { subtype, .. } => {
                ErrorKey::internal("configuration_error", subtype.as_deref())
            }
            InternalError::ScriptError { subtype, .. } => {
                ErrorKey::internal("script_error", subtype.as_deref())
            }
            InternalError::SerializeError { subtype, .. } => {
                ErrorKey::internal("serialize_error", subtype.as_deref())
            }
            InternalError::DeserializeError { subtype, .. } => {
                ErrorKey::internal("deserialize_error", subtype.as_deref())
            }
        }
    }

    fn message(&self) -> ErrorMessage {
        match self {
            InternalError::UnknownError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::UniqueFieldViolation { message, .. } => {
                ErrorMessage(message.to_string())
            }
            InternalError::Timeout { message, .. } => ErrorMessage(message.to_string()),
            InternalError::ConnectionError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::KeyNotFound { message, .. } => ErrorMessage(message.to_string()),
            InternalError::InvalidArgument { message, .. } => ErrorMessage(message.to_string()),
            InternalError::IOErr { message, .. } => ErrorMessage(message.to_string()),
            InternalError::EncryptionError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::DecryptionError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::ConfigurationError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::ScriptError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::SerializeError { message, .. } => ErrorMessage(message.to_string()),
            InternalError::DeserializeError { message, .. } => ErrorMessage(message.to_string()),
        }
    }

    // TODO: Implement this
    fn meta(&self) -> Option<Value> {
        match self {
            InternalError::UnknownError { meta, .. } => meta.clone(),
            InternalError::UniqueFieldViolation { meta, .. } => meta.clone(),
            InternalError::Timeout { meta, .. } => meta.clone(),
            InternalError::ConnectionError { meta, .. } => meta.clone(),
            InternalError::KeyNotFound { meta, .. } => meta.clone(),
            InternalError::InvalidArgument { meta, .. } => meta.clone(),
            InternalError::IOErr { meta, .. } => meta.clone(),
            InternalError::EncryptionError { meta, .. } => meta.clone(),
            InternalError::DecryptionError { meta, .. } => meta.clone(),
            InternalError::ConfigurationError { meta, .. } => meta.clone(),
            InternalError::ScriptError { meta, .. } => meta.clone(),
            InternalError::SerializeError { meta, .. } => meta.clone(),
            InternalError::DeserializeError { meta, .. } => meta.clone(),
        }
    }
}

impl Debug for InternalError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        writeln!(f, "{}\n", &self)?;
        let mut current = self.source();

        while let Some(cause) = current {
            writeln!(f, "Caused by:\n\t{}", cause)?;
            current = cause.source();
        }

        Ok(())
    }
}

#[derive(ThisError, Clone, Eq, PartialEq, Serialize, AsRefStr)]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "PascalCase")]
pub enum ApplicationError {
    #[error("Bad Request: {}", .message)]
    BadRequest {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Conflict: {}", .message)]
    Conflict {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Forbidden: {}", .message)]
    Forbidden {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Internal Server Error: {}", .message)]
    InternalServerError {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Method Not Allowed: {}", .message)]
    MethodNotAllowed {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Not Found: {}", .message)]
    NotFound {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Not Implemented: {}", .message)]
    NotImplemented {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Precondition Failed: {}", .message)]
    FailedDependency {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Service Unavailable: {}", .message)]
    ServiceUnavailable {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Too Many Requests: {}", .message)]
    TooManyRequests {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Unauthorized: {}", .message)]
    Unauthorized {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
    #[error("Unprocessable Entity: {}", .message)]
    UnprocessableEntity {
        message: String,
        subtype: Option<String>,
        meta: Option<Value>,
    },
}

impl From<anyhow::Error> for ApplicationError {
    fn from(error: anyhow::Error) -> Self {
        match error.downcast_ref::<ApplicationError>() {
            Some(integration_error) => integration_error.clone(),
            None => ApplicationError::InternalServerError {
                message: error.to_string(),
                subtype: None,
                meta: None,
            },
        }
    }
}

impl ApplicationError {
    pub fn bad_request(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::BadRequest {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn conflict(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::Conflict {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn forbidden(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::Forbidden {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn internal_server_error(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::InternalServerError {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn method_not_allowed(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::MethodNotAllowed {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn not_found(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::NotFound {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn not_implemented(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::NotImplemented {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn failed_dependency(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::FailedDependency {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn service_unavailable(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::ServiceUnavailable {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn too_many_requests(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::TooManyRequests {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn unauthorized(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::Unauthorized {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    pub fn unprocessable_entity(message: &str, subtype: Option<&str>) -> IntegrationOSError {
        IntegrationOSError::application(ApplicationError::UnprocessableEntity {
            message: message.to_string(),
            subtype: subtype.map(|s| s.to_string().snake_case()),
            meta: None,
        })
    }

    fn set_meta(self, meta: Value) -> Self {
        match self {
            ApplicationError::BadRequest {
                message, subtype, ..
            } => ApplicationError::BadRequest {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::Conflict {
                message, subtype, ..
            } => ApplicationError::Conflict {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::Forbidden {
                message, subtype, ..
            } => ApplicationError::Forbidden {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::InternalServerError {
                message, subtype, ..
            } => ApplicationError::InternalServerError {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::MethodNotAllowed {
                message, subtype, ..
            } => ApplicationError::MethodNotAllowed {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::NotFound {
                message, subtype, ..
            } => ApplicationError::NotFound {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::NotImplemented {
                message, subtype, ..
            } => ApplicationError::NotImplemented {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::FailedDependency {
                message, subtype, ..
            } => ApplicationError::FailedDependency {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::ServiceUnavailable {
                message, subtype, ..
            } => ApplicationError::ServiceUnavailable {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::TooManyRequests {
                message, subtype, ..
            } => ApplicationError::TooManyRequests {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::Unauthorized {
                message, subtype, ..
            } => ApplicationError::Unauthorized {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
            ApplicationError::UnprocessableEntity {
                message, subtype, ..
            } => ApplicationError::UnprocessableEntity {
                message: message.clone(),
                subtype: subtype.clone(),
                meta: Some(meta),
            },
        }
    }
}

impl ErrorMeta for ApplicationError {
    fn code(&self) -> ErrorCode {
        match self {
            ApplicationError::BadRequest { .. } => ErrorCode(2000),
            ApplicationError::Conflict { .. } => ErrorCode(2001),
            ApplicationError::Forbidden { .. } => ErrorCode(2002),
            ApplicationError::InternalServerError { .. } => ErrorCode(2003),
            ApplicationError::MethodNotAllowed { .. } => ErrorCode(2004),
            ApplicationError::NotFound { .. } => ErrorCode(2005),
            ApplicationError::NotImplemented { .. } => ErrorCode(2006),
            ApplicationError::FailedDependency { .. } => ErrorCode(2007),
            ApplicationError::ServiceUnavailable { .. } => ErrorCode(2008),
            ApplicationError::TooManyRequests { .. } => ErrorCode(2009),
            ApplicationError::Unauthorized { .. } => ErrorCode(2010),
            ApplicationError::UnprocessableEntity { .. } => ErrorCode(2011),
        }
    }

    fn key(&self) -> ErrorKey {
        match self {
            ApplicationError::BadRequest { subtype, .. } => {
                ErrorKey::application("bad_request", subtype.as_deref())
            }
            ApplicationError::Conflict { subtype, .. } => {
                ErrorKey::application("conflict", subtype.as_deref())
            }
            ApplicationError::Forbidden { subtype, .. } => {
                ErrorKey::application("forbidden", subtype.as_deref())
            }
            ApplicationError::InternalServerError { subtype, .. } => {
                ErrorKey::application("internal_server_error", subtype.as_deref())
            }
            ApplicationError::MethodNotAllowed { subtype, .. } => {
                ErrorKey::application("method_not_allowed", subtype.as_deref())
            }
            ApplicationError::NotFound { subtype, .. } => {
                ErrorKey::application("not_found", subtype.as_deref())
            }
            ApplicationError::NotImplemented { subtype, .. } => {
                ErrorKey::application("not_implemented", subtype.as_deref())
            }
            ApplicationError::FailedDependency { subtype, .. } => {
                ErrorKey::application("failed_dependency", subtype.as_deref())
            }
            ApplicationError::ServiceUnavailable { subtype, .. } => {
                ErrorKey::application("service_unavailable", subtype.as_deref())
            }
            ApplicationError::TooManyRequests { subtype, .. } => {
                ErrorKey::application("too_many_requests", subtype.as_deref())
            }
            ApplicationError::Unauthorized { subtype, .. } => {
                ErrorKey::application("unauthorized", subtype.as_deref())
            }
            ApplicationError::UnprocessableEntity { subtype, .. } => {
                ErrorKey::application("unprocessable_entity", subtype.as_deref())
            }
        }
    }

    fn message(&self) -> ErrorMessage {
        match self {
            ApplicationError::BadRequest { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::Conflict { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::Forbidden { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::InternalServerError { message, .. } => {
                ErrorMessage(message.to_string())
            }
            ApplicationError::MethodNotAllowed { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::NotFound { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::NotImplemented { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::FailedDependency { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::ServiceUnavailable { message, .. } => {
                ErrorMessage(message.to_string())
            }
            ApplicationError::TooManyRequests { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::Unauthorized { message, .. } => ErrorMessage(message.to_string()),
            ApplicationError::UnprocessableEntity { message, .. } => {
                ErrorMessage(message.to_string())
            }
        }
    }

    fn meta(&self) -> Option<Value> {
        match self {
            ApplicationError::BadRequest { meta, .. } => meta.clone(),
            ApplicationError::Conflict { meta, .. } => meta.clone(),
            ApplicationError::Forbidden { meta, .. } => meta.clone(),
            ApplicationError::InternalServerError { meta, .. } => meta.clone(),
            ApplicationError::MethodNotAllowed { meta, .. } => meta.clone(),
            ApplicationError::NotFound { meta, .. } => meta.clone(),
            ApplicationError::NotImplemented { meta, .. } => meta.clone(),
            ApplicationError::FailedDependency { meta, .. } => meta.clone(),
            ApplicationError::ServiceUnavailable { meta, .. } => meta.clone(),
            ApplicationError::TooManyRequests { meta, .. } => meta.clone(),
            ApplicationError::Unauthorized { meta, .. } => meta.clone(),
            ApplicationError::UnprocessableEntity { meta, .. } => meta.clone(),
        }
    }
}

impl Debug for ApplicationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        writeln!(f, "{}\n", &self)?;
        let mut current = self.source();

        while let Some(cause) = current {
            writeln!(f, "Caused by:\n\t{}", cause)?;
            current = cause.source();
        }

        Ok(())
    }
}

impl From<InternalError> for ApplicationError {
    fn from(error: InternalError) -> Self {
        match error {
            InternalError::Timeout { .. }
            | InternalError::ConnectionError { .. }
            | InternalError::IOErr { .. }
            | InternalError::EncryptionError { .. }
            | InternalError::DecryptionError { .. }
            | InternalError::ScriptError { .. }
            | InternalError::ConfigurationError { .. }
            | InternalError::UnknownError { .. } => ApplicationError::InternalServerError {
                message: "An unknown error occurred".into(),
                subtype: None,
                meta: None,
            },
            InternalError::UniqueFieldViolation {
                message,
                subtype,
                meta,
            } => ApplicationError::Conflict {
                message,
                subtype,
                meta,
            },
            InternalError::KeyNotFound {
                message,
                subtype,
                meta,
            } => ApplicationError::NotFound {
                message,
                subtype,
                meta,
            },
            InternalError::InvalidArgument {
                message,
                subtype,
                meta,
            }
            | InternalError::SerializeError {
                message,
                subtype,
                meta,
            }
            | InternalError::DeserializeError {
                message,
                subtype,
                meta,
            } => ApplicationError::BadRequest {
                message,
                subtype,
                meta,
            },
        }
    }
}

#[derive(ThisError, Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum IntegrationOSError {
    Internal(InternalError),
    Application(ApplicationError),
}

impl AsRef<str> for IntegrationOSError {
    fn as_ref(&self) -> &str {
        match self {
            IntegrationOSError::Internal(e) => e.as_ref(),
            IntegrationOSError::Application(e) => e.as_ref(),
        }
    }
}

impl From<anyhow::Error> for IntegrationOSError {
    fn from(error: anyhow::Error) -> Self {
        match error.downcast_ref::<IntegrationOSError>() {
            Some(integration_error) => match integration_error {
                internal @ IntegrationOSError::Internal(_) => internal.clone(),
                application @ IntegrationOSError::Application(_) => application.clone(),
            },
            None => IntegrationOSError::Internal(InternalError::UnknownError {
                message: error.to_string(),
                subtype: None,
                meta: None,
            }),
        }
    }
}

impl From<Arc<IntegrationOSError>> for IntegrationOSError {
    fn from(error: Arc<IntegrationOSError>) -> Self {
        Arc::unwrap_or_clone(error)
    }
}

impl<'a> From<&'a IntegrationOSError> for StatusCode {
    fn from(value: &'a IntegrationOSError) -> Self {
        match value {
            IntegrationOSError::Internal(e) => match e {
                InternalError::UniqueFieldViolation { .. } => StatusCode::CONFLICT,
                InternalError::Timeout { .. } => StatusCode::GATEWAY_TIMEOUT,
                InternalError::ConnectionError { .. } => StatusCode::BAD_GATEWAY,
                InternalError::KeyNotFound { .. } => StatusCode::NOT_FOUND,
                InternalError::InvalidArgument { .. }
                | InternalError::SerializeError { .. }
                | InternalError::DeserializeError { .. } => StatusCode::BAD_REQUEST,
                InternalError::UnknownError { .. }
                | InternalError::IOErr { .. }
                | InternalError::EncryptionError { .. }
                | InternalError::ConfigurationError { .. }
                | InternalError::ScriptError { .. }
                | InternalError::DecryptionError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            },
            IntegrationOSError::Application(e) => match e {
                ApplicationError::BadRequest { .. } => StatusCode::BAD_REQUEST,
                ApplicationError::Conflict { .. } => StatusCode::CONFLICT,
                ApplicationError::Forbidden { .. } => StatusCode::FORBIDDEN,
                ApplicationError::InternalServerError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
                ApplicationError::MethodNotAllowed { .. } => StatusCode::METHOD_NOT_ALLOWED,
                ApplicationError::NotFound { .. } => StatusCode::NOT_FOUND,
                ApplicationError::NotImplemented { .. } => StatusCode::NOT_IMPLEMENTED,
                ApplicationError::FailedDependency { .. } => StatusCode::FAILED_DEPENDENCY,
                ApplicationError::ServiceUnavailable { .. } => StatusCode::SERVICE_UNAVAILABLE,
                ApplicationError::TooManyRequests { .. } => StatusCode::TOO_MANY_REQUESTS,
                ApplicationError::Unauthorized { .. } => StatusCode::UNAUTHORIZED,
                ApplicationError::UnprocessableEntity { .. } => StatusCode::UNPROCESSABLE_ENTITY,
            },
        }
    }
}

impl From<mongodb::error::Error> for IntegrationOSError {
    fn from(err: mongodb::error::Error) -> Self {
        match *err.kind {
            mongodb::error::ErrorKind::InvalidArgument { message, .. } => {
                InternalError::invalid_argument(&message, None)
            }
            mongodb::error::ErrorKind::Authentication { message, .. } => {
                InternalError::connection_error(&message, Some("Authentication failed"))
            }
            mongodb::error::ErrorKind::BsonDeserialization(error) => {
                InternalError::invalid_argument(
                    &error.to_string(),
                    Some("BSON deserialization error"),
                )
            }
            mongodb::error::ErrorKind::BsonSerialization(error) => InternalError::invalid_argument(
                &error.to_string(),
                Some("BSON serialization error"),
            ),
            mongodb::error::ErrorKind::BulkWrite(error) => InternalError::unknown(
                &error
                    .write_errors
                    .into_iter()
                    .map(|e| e.into_iter().map(|e| e.message).collect())
                    .collect::<Vec<String>>()
                    .join(", "),
                Some("Bulk write error"),
            ),
            mongodb::error::ErrorKind::Command(error) => {
                let code = error.code;
                if code == 11000 {
                    InternalError::unique_field_violation(
                        &error.message,
                        Some("A document with the same unique key already exists"),
                    )
                } else {
                    InternalError::unknown(&error.message, Some("Command error"))
                }
            }
            mongodb::error::ErrorKind::DnsResolve { message, .. } => {
                InternalError::connection_error(&message, Some("DNS resolution failed"))
            }
            mongodb::error::ErrorKind::GridFs { .. } => InternalError::unknown(
                "GridFS error",
                Some("An error occurred while interacting with GridFS"),
            ),
            mongodb::error::ErrorKind::Internal { message, .. } => {
                InternalError::unknown(&message, Some("Internal error"))
            }
            mongodb::error::ErrorKind::Io(error) => InternalError::io_err(&error.to_string(), None),
            mongodb::error::ErrorKind::ConnectionPoolCleared { message, .. } => {
                InternalError::connection_error(&message, Some("Connection pool cleared"))
            }
            mongodb::error::ErrorKind::InvalidResponse { message, .. } => {
                InternalError::invalid_argument(&message, Some("Invalid response"))
            }
            mongodb::error::ErrorKind::ServerSelection { message, .. } => {
                InternalError::connection_error(&message, Some("Server selection failed"))
            }
            mongodb::error::ErrorKind::SessionsNotSupported => {
                InternalError::invalid_argument("Sessions not supported", None)
            }
            mongodb::error::ErrorKind::InvalidTlsConfig { message, .. } => {
                InternalError::connection_error(&message, Some("Invalid TLS configuration"))
            }
            mongodb::error::ErrorKind::Write(error) => match error {
                WriteFailure::WriteConcernError(err) => {
                    let code = err.code;

                    if code == 11000 {
                        InternalError::unique_field_violation(
                            &err.message,
                            Some("A document with the same unique key already exists"),
                        )
                    } else {
                        InternalError::unknown(&err.message, Some("Write concern error"))
                    }
                }
                WriteFailure::WriteError(err) => {
                    let code = err.code;
                    if code == 11000 {
                        InternalError::unique_field_violation(
                            &err.message,
                            Some("A document with the same unique key already exists"),
                        )
                    } else {
                        InternalError::unknown(&err.message, Some("Write error"))
                    }
                }
                _ => InternalError::unknown("Write error", Some("An error occurred while writing")),
            },
            mongodb::error::ErrorKind::Transaction { message, .. } => {
                InternalError::unknown(&message, Some("Transaction error"))
            }
            mongodb::error::ErrorKind::IncompatibleServer { message, .. } => {
                InternalError::connection_error(&message, Some("Incompatible server"))
            }
            mongodb::error::ErrorKind::MissingResumeToken => {
                InternalError::invalid_argument("Missing resume token", None)
            }
            mongodb::error::ErrorKind::Custom(_) => InternalError::unknown(
                "Unknown error",
                Some("An error occurred with the MongoDB driver"),
            ),
            mongodb::error::ErrorKind::Shutdown => {
                InternalError::unknown("Shutdown error", Some("The MongoDB driver has shut down"))
            }
            _ => InternalError::unknown("Unknown error", Some("An unknown error occurred")),
        }
    }
}

impl From<IntegrationOSError> for StatusCode {
    fn from(value: IntegrationOSError) -> Self {
        (&value).into()
    }
}

impl IntegrationOSError {
    pub fn status(&self) -> u16 {
        StatusCode::from(self).as_u16()
    }

    fn internal(internal: InternalError) -> Self {
        IntegrationOSError::Internal(internal)
    }

    fn application(application: ApplicationError) -> Self {
        IntegrationOSError::Application(application)
    }

    pub fn from_err_code(status: StatusCode, message: &str, subtype: Option<&str>) -> Self {
        let message = message.to_string();
        let subtype = subtype.map(|s| s.to_string());
        let meta = None;
        match status {
            StatusCode::BAD_REQUEST => {
                IntegrationOSError::application(ApplicationError::BadRequest {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::CONFLICT => IntegrationOSError::application(ApplicationError::Conflict {
                message,
                subtype,
                meta,
            }),

            StatusCode::FORBIDDEN => IntegrationOSError::application(ApplicationError::Forbidden {
                message,
                subtype,
                meta,
            }),

            StatusCode::INTERNAL_SERVER_ERROR => {
                IntegrationOSError::application(ApplicationError::InternalServerError {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::METHOD_NOT_ALLOWED => {
                IntegrationOSError::application(ApplicationError::MethodNotAllowed {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::NOT_FOUND => IntegrationOSError::application(ApplicationError::NotFound {
                message,
                subtype,
                meta,
            }),

            StatusCode::NOT_IMPLEMENTED => {
                IntegrationOSError::application(ApplicationError::NotImplemented {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::FAILED_DEPENDENCY => {
                IntegrationOSError::application(ApplicationError::FailedDependency {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::SERVICE_UNAVAILABLE => {
                IntegrationOSError::application(ApplicationError::ServiceUnavailable {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::TOO_MANY_REQUESTS => {
                IntegrationOSError::application(ApplicationError::TooManyRequests {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::UNAUTHORIZED => {
                IntegrationOSError::application(ApplicationError::Unauthorized {
                    message,
                    subtype,
                    meta,
                })
            }

            StatusCode::UNPROCESSABLE_ENTITY => {
                IntegrationOSError::application(ApplicationError::UnprocessableEntity {
                    message,
                    subtype,
                    meta,
                })
            }

            _ => {
                if status.is_client_error() {
                    IntegrationOSError::application(ApplicationError::BadRequest {
                        message,
                        subtype,
                        meta,
                    })
                } else {
                    IntegrationOSError::internal(InternalError::IOErr {
                        message: format!(
                            "Unknown error with status code: {}, message: {}",
                            status, message
                        ),
                        subtype,
                        meta,
                    })
                }
            }
        }
    }

    pub(crate) fn as_application(&self) -> IntegrationOSError {
        match self {
            IntegrationOSError::Application(e) => IntegrationOSError::Application(e.clone()),
            IntegrationOSError::Internal(e) => IntegrationOSError::Application(e.clone().into()),
        }
    }

    pub(crate) fn as_json(&self) -> serde_json::Value {
        json!({
            "type": self.as_ref(),
            "code": self.code().as_u16(),
            "status": StatusCode::from(self).as_u16(),
            "key": self.key().to_string(),
            "message": self.message().to_string(),
            "meta": self.meta().unwrap_or_default(),
        })
    }

    pub fn set_meta(self, meta: &Value) -> Self {
        match self {
            IntegrationOSError::Internal(e) => {
                IntegrationOSError::internal(e.set_meta(meta.clone()))
            }
            IntegrationOSError::Application(e) => {
                IntegrationOSError::application(e.set_meta(meta.clone()))
            }
        }
    }

    pub fn is_internal(&self) -> bool {
        matches!(self, IntegrationOSError::Internal(_))
    }

    pub fn is_application(&self) -> bool {
        matches!(self, IntegrationOSError::Application(_))
    }
}

impl ErrorMeta for IntegrationOSError {
    fn code(&self) -> ErrorCode {
        match self {
            IntegrationOSError::Internal(e) => e.code(),
            IntegrationOSError::Application(e) => e.code(),
        }
    }

    fn key(&self) -> ErrorKey {
        match self {
            IntegrationOSError::Internal(e) => e.key(),
            IntegrationOSError::Application(e) => e.key(),
        }
    }

    fn message(&self) -> ErrorMessage {
        match self {
            IntegrationOSError::Internal(e) => e.message(),
            IntegrationOSError::Application(e) => e.message(),
        }
    }

    fn meta(&self) -> Option<Value> {
        match self {
            IntegrationOSError::Internal(e) => e.meta(),
            IntegrationOSError::Application(e) => e.meta(),
        }
    }
}

impl Display for IntegrationOSError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            IntegrationOSError::Internal(e) => write!(f, "{}", e),
            IntegrationOSError::Application(e) => write!(f, "{}", e),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_function() {
        let internal_error: IntegrationOSError = InternalError::unknown("test", None);

        assert_eq!(internal_error.code(), ErrorCode(1000),);
        assert_eq!(internal_error.key(), ErrorKey::internal("unknown", None),);
        assert_eq!(internal_error.message(), ErrorMessage("test".to_string()),);
    }

    #[test]
    fn test_error_code() {
        let code = ErrorCode(400);
        assert_eq!(code.to_string(), "400");
    }

    #[test]
    fn test_error_key() {
        let key = ErrorKey::internal("test", None);
        assert_eq!(key.to_string(), "err::internal::test");
    }

    #[test]
    fn test_error_message() {
        let message = ErrorMessage("test".to_string());
        assert_eq!(message.to_string(), "test");
    }

    #[test]
    fn test_interoperability_between_anyhow_error_and_domain_error() {
        let err = InternalError::unknown("test", None);
        let any_err: anyhow::Error = err.clone().into();
        let code: ErrorCode = any_err.downcast_ref::<IntegrationOSError>().unwrap().code();
        let message: ErrorMessage = any_err
            .downcast_ref::<IntegrationOSError>()
            .unwrap()
            .message();
        let key: ErrorKey = any_err.downcast_ref::<IntegrationOSError>().unwrap().key();

        assert_eq!(code, ErrorCode(1000));
        assert_eq!(message, ErrorMessage("test".to_string()));
        assert_eq!(key, ErrorKey::internal("unknown", None));
    }

    #[test]
    fn test_round_trip_between_domain_error_and_anyhow_error() {
        let err = InternalError::unknown("test", None);
        let any_err: anyhow::Error = err.clone().into();
        let round_trip_err = any_err.into();

        let app_err = ApplicationError::bad_request("test", None);
        let any_app_err: anyhow::Error = app_err.clone().into();
        let round_trip_app_err = any_app_err.into();

        assert_eq!(err, round_trip_err);
        assert_eq!(app_err, round_trip_app_err);
    }

    #[test]
    fn from_internal_error_to_application_error() {
        let internal_error = InternalError::UniqueFieldViolation {
            message: "test".to_string(),
            subtype: None,
            meta: None,
        };
        let app_error: ApplicationError = internal_error.into();

        assert_eq!(
            app_error,
            ApplicationError::Conflict {
                message: "test".to_string(),
                subtype: None,
                meta: None
            }
        );
    }
}
