use axum::{
    extract::{Multipart, State},
    http::StatusCode,
    Json,
};
use std::sync::Arc;

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

/// Request body for profile updates.
#[derive(Debug, serde::Deserialize)]
pub struct UpdateProfileRequest {
    pub display_name: Option<String>,
}

/// Update the user's display name. Returns a new token + user.
pub async fn update_profile(
    auth: AuthUser,
    State(state): State<Arc<AppState>>,
    Json(body): Json<UpdateProfileRequest>,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    let display_name = body.display_name.unwrap_or(auth.display_name.clone());

    if display_name.trim().is_empty() {
        return Err((
            StatusCode::BAD_REQUEST,
            "Display name cannot be empty".into(),
        ));
    }

    sqlx::query("UPDATE users SET display_name = ? WHERE id = ?")
        .bind(&display_name)
        .bind(&auth.user_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // Fetch the updated avatar_url
    let avatar_url: Option<String> =
        sqlx::query_scalar("SELECT avatar_url FROM users WHERE id = ?")
            .bind(&auth.user_id)
            .fetch_optional(&state.db)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
            .flatten();

    // Issue a new token with the updated display name
    let token = create_token(&auth.user_id, &auth.email, &display_name, &state.jwt_secret)
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

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

/// Upload a custom avatar image. Saves to uploads/avatars/<user_id>.ext
pub async fn upload_avatar(
    auth: AuthUser,
    State(state): State<Arc<AppState>>,
    mut multipart: Multipart,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    let field = multipart
        .next_field()
        .await
        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?
        .ok_or((StatusCode::BAD_REQUEST, "No file uploaded".into()))?;

    let content_type = field
        .content_type()
        .unwrap_or("application/octet-stream")
        .to_string();

    // Only allow images
    let ext = match content_type.as_str() {
        "image/png" => "png",
        "image/jpeg" | "image/jpg" => "jpg",
        "image/gif" => "gif",
        "image/webp" => "webp",
        _ => {
            return Err((
                StatusCode::BAD_REQUEST,
                "Only PNG, JPG, GIF, and WebP images are allowed".into(),
            ))
        }
    };

    let data = field
        .bytes()
        .await
        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

    // Limit to 2MB
    if data.len() > 2 * 1024 * 1024 {
        return Err((StatusCode::BAD_REQUEST, "Avatar must be under 2MB".into()));
    }

    // Save to uploads/avatars/
    let upload_dir = std::path::Path::new("uploads/avatars");
    tokio::fs::create_dir_all(upload_dir)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // Remove any old avatar files for this user
    if let Ok(mut entries) = tokio::fs::read_dir(upload_dir).await {
        while let Ok(Some(entry)) = entries.next_entry().await {
            let name = entry.file_name().to_string_lossy().to_string();
            if name.starts_with(&auth.user_id) {
                let _ = tokio::fs::remove_file(entry.path()).await;
            }
        }
    }

    let filename = format!("{}.{}", auth.user_id, ext);
    let filepath = upload_dir.join(&filename);

    tokio::fs::write(&filepath, &data)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let avatar_url = format!("/uploads/avatars/{}", filename);

    // Update DB
    sqlx::query("UPDATE users SET avatar_url = ? WHERE id = ?")
        .bind(&avatar_url)
        .bind(&auth.user_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

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

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

/// Remove the user's custom avatar, reverting to Gravatar.
pub async fn delete_avatar(
    auth: AuthUser,
    State(state): State<Arc<AppState>>,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    // Remove files
    let upload_dir = std::path::Path::new("uploads/avatars");
    if let Ok(mut entries) = tokio::fs::read_dir(upload_dir).await {
        while let Ok(Some(entry)) = entries.next_entry().await {
            let name = entry.file_name().to_string_lossy().to_string();
            if name.starts_with(&auth.user_id) {
                let _ = tokio::fs::remove_file(entry.path()).await;
            }
        }
    }

    // Clear in DB
    sqlx::query("UPDATE users SET avatar_url = NULL WHERE id = ?")
        .bind(&auth.user_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

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

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