use std::fmt::{Display, Formatter};

use actix_web::http::StatusCode;
use actix_web::HttpResponse;
use emgauwa_lib::db::errors::DatabaseError;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};

#[derive(Debug)]
pub enum ApiError {
	BadUid,
	ProtectedSchedule,
	DatabaseError(DatabaseError),
	InternalError(String),
}

impl ApiError {
	fn get_code(&self) -> StatusCode {
		match self {
			ApiError::BadUid => StatusCode::BAD_REQUEST,
			ApiError::ProtectedSchedule => StatusCode::FORBIDDEN,
			ApiError::DatabaseError(db_error) => db_error.get_code(),
			ApiError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
		}
	}
}

impl Serialize for ApiError {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		let mut s = serializer.serialize_struct("error", 2)?;
		s.serialize_field("code", &self.get_code().as_u16())?;
		s.serialize_field("description", &String::from(self))?;
		s.end()
	}
}

impl From<&ApiError> for String {
	fn from(err: &ApiError) -> Self {
		match err {
			ApiError::BadUid => String::from("the uid is in a bad format"),
			ApiError::ProtectedSchedule => String::from("the targeted schedule is protected"),
			ApiError::DatabaseError(db_err) => String::from(db_err),
			ApiError::InternalError(msg) => msg.clone(),
		}
	}
}

impl From<&ApiError> for HttpResponse {
	fn from(err: &ApiError) -> Self {
		HttpResponse::build(err.get_code()).json(err)
	}
}

impl Display for ApiError {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}: {}", self.get_code(), String::from(self))
	}
}

impl actix_web::error::ResponseError for ApiError {
	fn status_code(&self) -> StatusCode {
		self.get_code()
	}

	fn error_response(&self) -> HttpResponse {
		HttpResponse::from(self)
	}
}

impl From<sqlx::Error> for ApiError {
	fn from(err: sqlx::Error) -> Self {
		ApiError::DatabaseError(DatabaseError::from(err))
	}
}

impl From<DatabaseError> for ApiError {
	fn from(err: DatabaseError) -> Self {
		ApiError::DatabaseError(err)
	}
}