use actix_web::http::StatusCode;
use serde::{Deserialize, Serialize};

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

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ErrorCode {
    NotFound,
    DatabaseError,
    UnAuthorized,
    Internal,
    NoSetup,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Error {
    code: ErrorCode,
    message: String,
}

impl Error {
    pub fn new(code: ErrorCode, message: &str) -> Self {
        Self {
            code,
            message: message.into(),
        }
    }
    pub fn not_found() -> Self {
        Self {
            code: ErrorCode::NotFound,
            message: "Resource not found".to_string(),
        }
    }
    pub fn unauthorized() -> Self {
        Self {
            code: ErrorCode::NotFound,
            message: "Unauthorzed".to_string(),
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(source: std::io::Error) -> Self {
        Self {
            code: ErrorCode::Internal,
            message: source.to_string(),
        }
    }
}

impl From<&str> for Error {
    fn from(s: &str) -> Self {
        Error {
            code: ErrorCode::Internal,
            message: s.into(),
        }
    }
}

impl actix_web::error::ResponseError for Error {
    fn status_code(&self) -> actix_web::http::StatusCode {
        match self.code {
            ErrorCode::UnAuthorized => StatusCode::UNAUTHORIZED,
            ErrorCode::NoSetup => StatusCode::UPGRADE_REQUIRED,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

impl From<sea_orm::DbErr> for Error {
    fn from(e: sea_orm::DbErr) -> Self {
        Self {
            code: ErrorCode::DatabaseError,
            message: e.to_string(),
        }
    }
}

impl From<actix_web::error::BlockingError> for Error {
    fn from(source: actix_web::error::BlockingError) -> Self {
        Self {
            code: ErrorCode::Internal,
            message: source.to_string(),
        }
    }
}

impl From<sqlx::Error> for Error {
    fn from(e: sqlx::Error) -> Self {
        Self {
            code: ErrorCode::DatabaseError,
            message: e.to_string(),
        }
    }
}

impl From<sqlx::migrate::MigrateError> for Error {
    fn from(e: sqlx::migrate::MigrateError) -> Self {
        Self {
            code: ErrorCode::DatabaseError,
            message: e.to_string(),
        }
    }
}
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}
