use actix_web::{HttpResponse, ResponseError};
use thiserror::Error;

#[derive(Debug, Error)]
pub(crate) enum AppError {
    #[error("`{0}`")]
    User(#[from] UserError),

    #[error("`{0}`")]
    Json(#[from] serde_json::Error),

    #[error("`{0}`")]
    Actix(#[from] actix_web::Error),

    #[error("`{0}`")]
    Db(#[from] sea_orm::DbErr),

    #[error("`{0}`")]
    MinioS3(#[from] minio::s3::error::Error),

    #[error("`{0}`")]
    Std(#[from] std::io::Error),
}

#[derive(Debug, Error)]
pub(crate) enum UserError {
    #[error("`username` field of `User` cannot be empty!")]
    EmptyUsername,

    #[error("`username` field of `User` cannot contain whitespaces!")]
    UsernameInvalidCharacter,

    #[error("`password` field of `User` cannot be empty!")]
    EmptyPassword,

    #[error("`password` field of `User` cannot contain whitespaces!")]
    PasswordInvalidCharacter,

    #[error("Could not find any `User` for id: `{0}`!")]
    NotFound(i64),

    #[error("Failed to login user!")]
    LoginFailed,

    #[error("User is not logged in!")]
    NotLoggedIn,

    #[error("Invalid authorization token!")]
    InvalidToken,

    #[error("Could not find any `User`!")]
    Empty,
}

impl ResponseError for AppError {
    fn status_code(&self) -> actix_web::http::StatusCode {
        match self {
            AppError::User(user_error) => match user_error {
                UserError::EmptyUsername => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
                UserError::UsernameInvalidCharacter => {
                    actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
                }
                UserError::EmptyPassword => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
                UserError::PasswordInvalidCharacter => {
                    actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
                }
                UserError::NotFound(_) => actix_web::http::StatusCode::NOT_FOUND,
                UserError::NotLoggedIn => actix_web::http::StatusCode::UNAUTHORIZED,
                UserError::Empty => actix_web::http::StatusCode::NOT_FOUND,
                UserError::LoginFailed => actix_web::http::StatusCode::NOT_FOUND,
                UserError::InvalidToken => actix_web::http::StatusCode::UNAUTHORIZED,
            },
            AppError::Actix(fail) => fail.as_response_error().status_code(),
            _ => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    fn error_response(&self) -> HttpResponse {
        let status_code = self.status_code();
        let response = HttpResponse::build(status_code).body(self.to_string());
        response
    }
}