use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};
use axum::{extract::State, http::StatusCode, Json};
use std::sync::Arc;
use uuid::Uuid;

use crate::{
    middleware::auth::{create_token, AuthUser},
    models::{self, AuthResponse, LoginRequest, RegisterRequest, UserPublic},
    AppState,
};

/// Create a new password-based account and immediately return a JWT.
pub async fn register(
    State(state): State<Arc<AppState>>,
    Json(body): Json<RegisterRequest>,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    // Normalize email: trim whitespace and lowercase for consistent matching
    let email = body.email.trim().to_lowercase();
    let display_name = body.display_name.trim().to_string();

    if email.is_empty() {
        return Err((StatusCode::BAD_REQUEST, "Email is required".into()));
    }
    if display_name.is_empty() {
        return Err((StatusCode::BAD_REQUEST, "Display name is required".into()));
    }

    // Check if email already exists (case-insensitive for safety with legacy data)
    let existing = sqlx::query_scalar::<_, String>("SELECT id FROM users WHERE LOWER(email) = ?")
        .bind(&email)
        .fetch_optional(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    if existing.is_some() {
        return Err((StatusCode::CONFLICT, "Email already registered".into()));
    }

    let user_id = Uuid::new_v4().to_string();
    let salt = SaltString::generate(&mut OsRng);
    let password_hash = Argon2::default()
        .hash_password(body.password.as_bytes(), &salt)
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
        .to_string();

    sqlx::query("INSERT INTO users (id, email, display_name, password_hash) VALUES (?, ?, ?, ?)")
        .bind(&user_id)
        .bind(&email)
        .bind(&display_name)
        .bind(&password_hash)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let token = create_token(&user_id, &email, &display_name, &state.jwt_secret)
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(AuthResponse {
        token,
        user: UserPublic {
            id: user_id,
            email: models::public_email(&email),
            display_name,
            avatar_url: None,
            nostr_pubkey: None,
        },
    }))
}

/// Authenticate an existing password-based account and return a fresh JWT.
pub async fn login(
    State(state): State<Arc<AppState>>,
    Json(body): Json<LoginRequest>,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    // Normalize email: trim whitespace and lowercase for case-insensitive matching
    let email = body.email.trim().to_lowercase();

    let user = sqlx::query_as::<_, (String, String, String, String, Option<String>)>(
        "SELECT id, email, display_name, password_hash, avatar_url FROM users WHERE LOWER(email) = ?",
    )
    .bind(&email)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
    .ok_or((StatusCode::UNAUTHORIZED, "Invalid credentials".into()))?;

    let (user_id, email, display_name, hash, avatar_url) = user;

    let parsed_hash =
        PasswordHash::new(&hash).map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Argon2::default()
        .verify_password(body.password.as_bytes(), &parsed_hash)
        .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid credentials".to_string()))?;

    let token = create_token(&user_id, &email, &display_name, &state.jwt_secret)
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(AuthResponse {
        token,
        user: UserPublic {
            id: user_id,
            email: models::public_email(&email),
            display_name,
            avatar_url,
            nostr_pubkey: None,
        },
    }))
}

/// Return the caller's current public profile information.
pub async fn me(
    auth: AuthUser,
    State(state): State<Arc<AppState>>,
) -> Result<Json<UserPublic>, (StatusCode, String)> {
    let row = sqlx::query_as::<_, (Option<String>, Option<String>)>(
        "SELECT avatar_url, nostr_pubkey FROM users WHERE id = ?",
    )
    .bind(&auth.user_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
    .unwrap_or((None, None));

    Ok(Json(UserPublic {
        id: auth.user_id,
        email: models::public_email(&auth.email),
        display_name: auth.display_name,
        avatar_url: row.0,
        nostr_pubkey: row.1,
    }))
}
