//! Admin user management: list, delete, promote/demote.
//!
//! All handlers are gated by `RequireAdmin` and rely on
//! `repo::user::admin_safe_*` for self-protection and the last-admin
//! invariant. Audit rows are written inside the same DB transaction as
//! the action they record.

use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::routing::{delete, get};
use axum::{Json, Router};
use serde::Deserialize;
use uuid::Uuid;

use crate::api::auth::{validate_password, validate_username};
use crate::api::pagination::PagedResponse;
use crate::app::AppState;
use crate::auth::extractor::RequireAdmin;
use crate::auth::password::hash_password_async;
use crate::domain::User;
use crate::error::{AppError, AppResult};
use crate::repo;

pub fn routes() -> Router<AppState> {
    Router::new()
        .route("/admin/users", get(list_users).post(create_user))
        .route(
            "/admin/users/:id",
            delete(delete_user).patch(update_user),
        )
}

#[derive(Debug, Deserialize, Default)]
pub struct ListUsersParams {
    #[serde(default)]
    pub search: Option<String>,
    #[serde(default = "default_limit")]
    pub limit: i64,
    #[serde(default)]
    pub offset: i64,
}

fn default_limit() -> i64 {
    50
}

async fn list_users(
    State(state): State<AppState>,
    _admin: RequireAdmin,
    Query(params): Query<ListUsersParams>,
) -> AppResult<Json<PagedResponse<User>>> {
    let limit = params.limit.clamp(1, 200);
    let offset = params.offset.max(0);
    let (items, total) = repo::user::list_with_total(
        &state.db,
        &repo::user::ListUsersQuery {
            search: params.search.filter(|s| !s.trim().is_empty()),
            limit,
            offset,
        },
    )
    .await?;
    Ok(Json(PagedResponse::with_total(items, limit, offset, total)))
}

#[derive(Debug, Deserialize)]
pub struct UpdateUserInput {
    pub is_admin: Option<bool>,
}

async fn update_user(
    State(state): State<AppState>,
    RequireAdmin(actor): RequireAdmin,
    Path(id): Path<Uuid>,
    Json(input): Json<UpdateUserInput>,
) -> AppResult<Json<User>> {
    let Some(is_admin) = input.is_admin else {
        return Err(AppError::InvalidInput(
            "no updatable fields supplied".into(),
        ));
    };
    let updated =
        repo::user::admin_safe_set_is_admin(&state.db, actor.id, id, is_admin).await?;
    Ok(Json(updated))
}

async fn delete_user(
    State(state): State<AppState>,
    RequireAdmin(actor): RequireAdmin,
    Path(id): Path<Uuid>,
) -> AppResult<StatusCode> {
    repo::user::admin_safe_delete(&state.db, actor.id, id).await?;
    Ok(StatusCode::NO_CONTENT)
}

#[derive(Debug, Deserialize)]
pub struct CreateUserInput {
    pub username: String,
    pub password: String,
    /// Defaults to false; admins may mint other admins in a single
    /// call. Doing it as one POST avoids a second audit row for the
    /// common "invite a co-admin" flow.
    #[serde(default)]
    pub is_admin: bool,
}

async fn create_user(
    State(state): State<AppState>,
    RequireAdmin(actor): RequireAdmin,
    Json(input): Json<CreateUserInput>,
) -> AppResult<(StatusCode, Json<User>)> {
    let username = input.username.trim();
    // Reuse the canonical self-register validators so the admin-create
    // path can never produce a username that self-register would
    // reject (and vice versa).
    validate_username(username)?;
    validate_password(&input.password)?;
    let pwhash = hash_password_async(input.password.clone()).await?;
    let user = repo::user::admin_create_user(
        &state.db,
        actor.id,
        username,
        &pwhash,
        input.is_admin,
    )
    .await?;
    Ok((StatusCode::CREATED, Json(user)))
}
