use actix_web::body::BoxBody;
use actix_web::dev::ServiceResponse;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode;
use actix_web::middleware::ErrorHandlerResponse;
use actix_web::{web, HttpResponse, ResponseError};
use derive_more::{Display, Error};

use super::SharedData;
use crate::template_utils::{render_template, ErrorMessage, TemplateArgs};

#[derive(Debug, Display, Error)]
pub enum UserError {
    NotFound,
    #[display(fmt = "Internal error. Try again later.")]
    InternalError,
}

impl ResponseError for UserError {
    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code())
            .insert_header(ContentType::html())
            .body(self.to_string())
    }

    fn status_code(&self) -> StatusCode {
        match *self {
            UserError::NotFound => StatusCode::NOT_FOUND,
            UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

pub fn generic_error<B>(
    res: ServiceResponse<B>,
) -> actix_web::Result<ErrorHandlerResponse<BoxBody>> {
    let data = res
        .request()
        .app_data::<web::Data<SharedData>>()
        .map(|t| t.get_ref());

    let status_code = res.response().status();
    let message = if let Some(error) = status_code.canonical_reason() {
        error
    } else {
        ""
    };

    let response = match data {
        Some(SharedData {
            handlebars,
            config: _,
        }) => {
            let args = TemplateArgs::builder()
                .error_page(ErrorMessage::new(message, status_code.as_u16()))
                .build();

            let body = render_template(handlebars, "error", &args)
                .map_err(|_| UserError::InternalError)?;

            HttpResponse::build(res.status())
                .content_type(ContentType::html())
                .body(body)
        }
        None => Err(UserError::InternalError)?,
    };

    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        res.into_parts().0,
        response.map_into_left_body(),
    )))
}
