use std::{borrow::Cow, error::Error, fmt::Display};

use serde::{Deserialize, Serialize};

use crate::prelude::reqwest;

#[derive(Debug)]
pub enum KeycloakError {
    ReqwestFailure(reqwest::Error),
    HttpFailure {
        status: u16,
        body: Option<KeycloakHttpError>,
        text: String,
    },
}

impl From<reqwest::Error> for KeycloakError {
    fn from(value: reqwest::Error) -> Self {
        KeycloakError::ReqwestFailure(value)
    }
}

impl Error for KeycloakError {}

impl Display for KeycloakError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KeycloakError::ReqwestFailure(e) => write!(f, "keycloak error (network): {e}"),
            KeycloakError::HttpFailure { status, body, text } => write!(
                f,
                "keycloak error (rest): {status} {}",
                body.as_ref()
                    .and_then(|e| e.message())
                    .unwrap_or_else(|| Cow::from(text))
            ),
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct KeycloakHttpError {
    pub error: Option<String>,
    pub error_description: Option<String>,
    #[serde(rename = "errorMessage")]
    pub error_message: Option<String>,
}

impl KeycloakHttpError {
    pub fn message(&self) -> Option<Cow<'_, str>> {
        self.error_message
            .as_deref()
            .map(Cow::from)
            .or_else(|| {
                self.error
                    .as_deref()
                    .map(|error| {
                        format!(
                            "{} [{error}]",
                            self.error_description.as_deref().unwrap_or("null")
                        )
                    })
                    .map(Cow::from)
            })
            .or_else(|| self.error_description.as_deref().map(Cow::from))
    }
}
