use diesel::{pg::PgConnection, result::Error as DieselError};
use uuid::Uuid;

use crate::{
    error::{AppError, AppResult},
    state::AppState,
};

pub trait EnsureEntity<T> {
    fn one(self) -> AppResult<T>;
    fn maybe(self) -> AppResult<Option<T>>;
}

impl<T> EnsureEntity<T> for Result<T, DieselError> {
    fn one(self) -> AppResult<T> {
        self.map_err(AppError::from)
    }

    fn maybe(self) -> AppResult<Option<T>> {
        match self {
            Ok(value) => Ok(Some(value)),
            Err(DieselError::NotFound) => Ok(None),
            Err(err) => Err(AppError::from(err)),
        }
    }
}

impl AppState {
    pub fn with_tenant_conn<F, T>(&self, tenant_id: Uuid, f: F) -> AppResult<T>
    where
        F: FnOnce(&mut PgConnection) -> AppResult<T>,
    {
        let mut conn = self.db_for_tenant(tenant_id)?;
        f(&mut conn)
    }
}

pub fn validate_bulk_ids(ids: &mut Vec<Uuid>, label: &str) -> AppResult<()> {
    if ids.is_empty() {
        return Err(AppError::bad_request(format!("{label} must not be empty")));
    }
    ids.sort_unstable();
    ids.dedup();
    Ok(())
}

pub trait IntoJsonResponse<T> {
    fn into_json(self) -> AppResult<axum::Json<T>>;
}

impl<T> IntoJsonResponse<T> for T {
    fn into_json(self) -> AppResult<axum::Json<T>> {
        Ok(axum::Json(self))
    }
}

pub fn no_content() -> AppResult<axum::http::StatusCode> {
    Ok(axum::http::StatusCode::NO_CONTENT)
}
