use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;

use validator::Validate;

use super::dto::{CreateUser, LoginRequest, TokenResponse, UpdateUser};
use super::error::Error;
use super::model::User;
use super::service;
use crate::auth::{hash_password, issue_token, verify_password};
use crate::state::AppState;

pub async fn list(State(state): State<AppState>) -> Result<Json<Vec<User>>, Error> {
    Ok(Json(service::list(&state.db).await?))
}

pub async fn show(
    State(state): State<AppState>,
    Path(id): Path<uuid::Uuid>,
) -> Result<Json<User>, Error> {
    Ok(Json(service::find(&state.db, id).await?))
}

pub async fn create(
    State(state): State<AppState>,
    Json(input): Json<CreateUser>,
) -> Result<(StatusCode, Json<User>), Error> {
    input.validate()?;
    let stored = CreateUser {
        password: hash_password(&input.password).map_err(|_| Error::Internal)?,
        ..input
    };
    let user = service::create(&state.db, &stored).await?;
    Ok((StatusCode::CREATED, Json(user)))
}

pub async fn update(
    State(state): State<AppState>,
    Path(id): Path<uuid::Uuid>,
    Json(input): Json<UpdateUser>,
) -> Result<Json<User>, Error> {
    input.validate()?;
    let stored = UpdateUser {
        password: hash_password(&input.password).map_err(|_| Error::Internal)?,
        ..input
    };
    Ok(Json(service::update(&state.db, id, &stored).await?))
}

pub async fn delete(
    State(state): State<AppState>,
    Path(id): Path<uuid::Uuid>,
) -> Result<StatusCode, Error> {
    service::delete(&state.db, id).await?;
    Ok(StatusCode::NO_CONTENT)
}

/// Public: register a new user (hashing the password) and return a session token.
///
/// `is_admin` is forced to `false` here — this is a public endpoint, so a client must not be
/// able to grant itself admin. Admins are created through the guarded `POST /users` route.
pub async fn register(
    State(state): State<AppState>,
    Json(input): Json<CreateUser>,
) -> Result<(StatusCode, Json<TokenResponse>), Error> {
    input.validate()?;
    let stored = CreateUser {
        password: hash_password(&input.password).map_err(|_| Error::Internal)?,
        is_admin: false,
        ..input
    };
    let user = service::create(&state.db, &stored).await?;
    let token = issue_token(&user.id).map_err(|_| Error::Internal)?;
    Ok((StatusCode::CREATED, Json(TokenResponse { token })))
}

/// Public: exchange email + password for a session token.
pub async fn login(
    State(state): State<AppState>,
    Json(input): Json<LoginRequest>,
) -> Result<Json<TokenResponse>, Error> {
    input.validate()?;
    let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = $1")
        .bind(&input.email)
        .fetch_optional(&state.db)
        .await?
        .ok_or(Error::Unauthorized)?;
    if !verify_password(&input.password, &user.password) {
        return Err(Error::Unauthorized);
    }
    let token = issue_token(&user.id).map_err(|_| Error::Internal)?;
    Ok(Json(TokenResponse { token }))
}
