//! Shared admin-authorization helpers used by both admin and public controllers.

use axum_extra::extract::cookie::CookieJar;
use loco_rs::prelude::*;

use crate::models::users;
use crate::controllers::auth::AUTH_COOKIE;
use crate::shared::settings;

/// Is `user` the configured admin (settings.admin_email)?
pub fn is_admin(ctx: &AppContext, user: &users::Model) -> bool {
    settings::get(ctx, "admin_email")
        .is_some_and(|email| user.email.eq_ignore_ascii_case(email))
}

/// Guard for admin handlers: requires a valid JWT whose user matches the
/// configured admin email. Returns the admin user, or an unauthorized error.
pub async fn current_admin(auth: auth::JWT, ctx: &AppContext) -> Result<users::Model> {
    let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?;
    if !is_admin(ctx, &user) {
        return unauthorized("admin only");
    }
    Ok(user)
}

/// Soft auth for public pages: returns the user behind a valid auth cookie, or
/// `None`. Never errors — used to decide chrome and post-login redirects, not
/// to gate protected handlers (use [`current_admin`] for that).
pub async fn current_user(ctx: &AppContext, jar: &CookieJar) -> Option<users::Model> {
    let cookie = jar.get(AUTH_COOKIE)?;
    let jwt_config = ctx.config.get_jwt_config().ok()?;
    let claims = loco_rs::auth::jwt::JWT::new(&jwt_config.secret)
        .validate(cookie.value())
        .ok()?;
    users::Model::find_by_pid(&ctx.db, &claims.claims.pid)
        .await
        .ok()
}

/// Soft check for public pages: does the request carry a valid admin auth
/// cookie? Never errors — used only to decide whether to show admin chrome.
pub async fn logged_in(ctx: &AppContext, jar: &CookieJar) -> bool {
    match current_user(ctx, jar).await {
        Some(user) => is_admin(ctx, &user),
        None => false,
    }
}

/// Nav chrome for storefront pages, resolved in one DB lookup. A customer is any
/// authenticated non-admin user; `customer_name`/`customer_account_type` are set
/// only for such a customer (used by the navbar profile menu). Everything is the
/// zero value for anonymous visitors and the name/type stay `None` for admins.
#[derive(Debug, Default)]
pub struct Chrome {
    pub logged_in_admin: bool,
    pub logged_in_customer: bool,
    pub customer_name: Option<String>,
    pub customer_account_type: Option<String>,
    /// Stored avatar image filename (served via `/images/{filename}`), set only
    /// for a logged-in customer who uploaded one. `None` -> initials fallback.
    pub customer_avatar: Option<String>,
}

pub async fn chrome(ctx: &AppContext, jar: &CookieJar) -> Chrome {
    chrome_from(ctx, current_user(ctx, jar).await.as_ref())
}

/// Same as [`chrome`] but from an already-loaded user, so a handler that needs
/// the user model (e.g. for pricing) can resolve chrome without a second lookup.
pub fn chrome_from(ctx: &AppContext, user: Option<&users::Model>) -> Chrome {
    match user {
        Some(user) if is_admin(ctx, user) => Chrome {
            logged_in_admin: true,
            ..Default::default()
        },
        Some(user) => Chrome {
            logged_in_customer: true,
            customer_name: Some(user.name.clone()),
            customer_account_type: Some(user.account_type.clone()),
            customer_avatar: user.avatar_id.clone(),
            ..Default::default()
        },
        None => Chrome::default(),
    }
}
