// error.rs
use rocket::{http::Status, response::{self, Responder}, Request, Response};
use thiserror::Error;
use rocket_dyn_templates::Template;
use rocket::serde::Serialize;

#[derive(Error, Debug)]
pub enum AppError {
	#[error("Not found")]
	NotFound,

	#[error("Unauthorized")]
	Unauthorised(String),

	#[error("Forbidden")]
	Forbidden,

	#[error("Bad request: {0}")]
	BadRequest(String),

	#[error("Database error: {0}")]
	Database(#[from] sqlx::Error),

	#[error("Internal error: {0}")]
	Internal(String),
}

impl AppError {
	pub fn internal(msg: impl Into<String>) -> Self {
		Self::Internal(msg.into())
	}

	pub fn bad_request(msg: impl Into<String>) -> Self {
		Self::BadRequest(msg.into())
	}
	pub fn unauthorised(msg: impl Into<String>) -> Self {
		Self::Unauthorised(msg.into())
	}
}

impl<'r> Responder<'r, 'static> for AppError {
	fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'static> {
		let status = match &self {
			AppError::NotFound => Status::NotFound,
			AppError::Unauthorised(_) => Status::Unauthorized,
			AppError::Forbidden => Status::Forbidden,
			AppError::BadRequest(_) => Status::BadRequest,
			AppError::Database(_) => Status::InternalServerError,
			AppError::Internal(_) => Status::InternalServerError,
		};

		// log internal errors
		if status == Status::InternalServerError {
			tracing::error!("Internal Server Error: {}", self);
		}

		Response::build()
			.status(status)
			.header(rocket::http::ContentType::Plain)
			.sized_body(
				self.to_string().len(),
				std::io::Cursor::new(self.to_string())
			)
			.ok()
	}
}

pub type ApiResult<T> = Result<T, AppError>;

#[derive(Serialize)]
struct ErrorContext {
    error_code: u16,
    error_message: &'static str,
    additional_info: &'static str,
    redirect: Option<RedirectContext>,
}

#[derive(Serialize)]
struct RedirectContext {
    url: &'static str,
    message: &'static str,
}

#[catch(404)]
pub async fn handle_404() -> Template {
    Template::render(
        "error",
        ErrorContext {
            error_code: 404,
            error_message: "Not Found",
            additional_info: "There's nothing here.",
            redirect: Some(RedirectContext {
                url: "/",
                message: "Home",
            }),
        },
    )
}

#[catch(401)]
pub async fn handle_401() -> Template {
    Template::render(
        "error",
        ErrorContext {
            error_code: 401,
            error_message: "Unauthorised",
            additional_info: "You are not authorised to access this resource.",
            redirect: Some(RedirectContext {
                url: "/login",
                message: "Login",
            }),
        },
    )
}

#[catch(default)]
pub async fn handle_default(status: Status, _request: &Request<'_>) -> Template {
    Template::render(
        "error",
        ErrorContext {
            error_code: status.code,
            error_message: "Unknown Error",
            additional_info: "I don't know what to do with this error.",
            redirect: None,
        },
    )
}