use axum::{
    extract::{Path, State},
    response::Json,
};
use serde::Deserialize;
use sqlx::Row;
use tracing::warn;

use super::{ApiResponse, DashboardState, auth};

// ── User management endpoints (admin-only) ──────────────────────────

pub(super) async fn handle_get_users(
    State(state): State<DashboardState>,
    headers: axum::http::HeaderMap,
) -> Json<ApiResponse<serde_json::Value>> {
    if let Err(e) = auth::require_admin(&state, &headers).await {
        return e;
    }

    let pool = &state.app_state.db_pool;

    let result = sqlx::query(
        "SELECT id, username, display_name, role, must_change_password, created_at FROM users ORDER BY created_at ASC",
    )
    .fetch_all(pool)
    .await;

    match result {
        Ok(rows) => {
            let users: Vec<serde_json::Value> = rows
                .into_iter()
                .map(|row| {
                    let username: String = row.get("username");
                    let display_name: Option<String> = row.get("display_name");
                    serde_json::json!({
                        "id": row.get::<i64, _>("id"),
                        "username": &username,
                        "display_name": display_name.as_deref().unwrap_or(&username),
                        "role": row.get::<String, _>("role"),
                        "must_change_password": row.get::<bool, _>("must_change_password"),
                        "created_at": row.get::<chrono::DateTime<chrono::Utc>, _>("created_at"),
                    })
                })
                .collect();

            Json(ApiResponse::success(serde_json::json!(users)))
        }
        Err(e) => {
            warn!("Failed to fetch users: {}", e);
            Json(ApiResponse::error("Failed to fetch users".to_string()))
        }
    }
}

#[derive(Deserialize)]
pub(super) struct CreateUserRequest {
    pub(super) username: String,
    pub(super) password: String,
    pub(super) display_name: Option<String>,
    pub(super) role: Option<String>,
}

pub(super) async fn handle_create_user(
    State(state): State<DashboardState>,
    headers: axum::http::HeaderMap,
    Json(payload): Json<CreateUserRequest>,
) -> Json<ApiResponse<serde_json::Value>> {
    if let Err(e) = auth::require_admin(&state, &headers).await {
        return e;
    }

    let pool = &state.app_state.db_pool;

    // Validate role
    let role = payload.role.as_deref().unwrap_or("viewer");
    if role != "admin" && role != "viewer" {
        return Json(ApiResponse::error("Role must be 'admin' or 'viewer'".to_string()));
    }

    // Validate username
    let username = payload.username.trim();
    if username.is_empty() || username.len() > 64 {
        return Json(ApiResponse::error("Username must be 1-64 characters".to_string()));
    }

    // Validate password
    if payload.password.len() < 4 {
        return Json(ApiResponse::error("Password must be at least 4 characters".to_string()));
    }

    let password_hash = match bcrypt::hash(&payload.password, 12) {
        Ok(h) => h,
        Err(_) => return Json(ApiResponse::error("Failed to hash password".to_string())),
    };

    let result = sqlx::query(
        r#"
        INSERT INTO users (username, password_hash, display_name, role, must_change_password)
        VALUES (?, ?, ?, ?, TRUE)
        RETURNING id, username, display_name, role, must_change_password, created_at
        "#,
    )
    .bind(username)
    .bind(&password_hash)
    .bind(&payload.display_name)
    .bind(role)
    .fetch_one(pool)
    .await;

    match result {
        Ok(row) => {
            let uname: String = row.get("username");
            let display_name: Option<String> = row.get("display_name");
            Json(ApiResponse::success(serde_json::json!({
                "id": row.get::<i64, _>("id"),
                "username": &uname,
                "display_name": display_name.as_deref().unwrap_or(&uname),
                "role": row.get::<String, _>("role"),
                "must_change_password": row.get::<bool, _>("must_change_password"),
                "created_at": row.get::<chrono::DateTime<chrono::Utc>, _>("created_at"),
            })))
        }
        Err(e) => {
            let msg = if e.to_string().contains("UNIQUE") {
                format!("Username '{}' already exists", username)
            } else {
                format!("Failed to create user: {}", e)
            };
            warn!("Failed to create user: {}", e);
            Json(ApiResponse::error(msg))
        }
    }
}

#[derive(Deserialize)]
pub(super) struct UpdateUserRequest {
    pub(super) display_name: Option<String>,
    pub(super) role: Option<String>,
    pub(super) password: Option<String>,
    pub(super) must_change_password: Option<bool>,
}

pub(super) async fn handle_update_user(
    State(state): State<DashboardState>,
    headers: axum::http::HeaderMap,
    Path(id): Path<i64>,
    Json(payload): Json<UpdateUserRequest>,
) -> Json<ApiResponse<serde_json::Value>> {
    if let Err(e) = auth::require_admin(&state, &headers).await {
        return e;
    }

    let pool = &state.app_state.db_pool;

    // Validate role if provided
    if let Some(ref role) = payload.role {
        if role != "admin" && role != "viewer" {
            return Json(ApiResponse::error("Role must be 'admin' or 'viewer'".to_string()));
        }
    }

    // Build dynamic update
    let mut sets = Vec::new();
    let mut string_binds: Vec<String> = Vec::new();
    let mut has_password = false;
    let mut has_must_change = false;

    if let Some(ref display_name) = payload.display_name {
        sets.push("display_name = ?");
        string_binds.push(display_name.clone());
    }
    if let Some(ref role) = payload.role {
        sets.push("role = ?");
        string_binds.push(role.clone());
    }
    if let Some(ref password) = payload.password {
        if password.len() < 4 {
            return Json(ApiResponse::error("Password must be at least 4 characters".to_string()));
        }
        let hash = match bcrypt::hash(password, 12) {
            Ok(h) => h,
            Err(_) => return Json(ApiResponse::error("Failed to hash password".to_string())),
        };
        sets.push("password_hash = ?");
        string_binds.push(hash);
        has_password = true;
    }
    if let Some(mcp) = payload.must_change_password {
        sets.push("must_change_password = ?");
        has_must_change = true;
        let _ = mcp; // used below in bind
    }

    if sets.is_empty() {
        return Json(ApiResponse::error("No fields to update".to_string()));
    }

    let sql = format!("UPDATE users SET {} WHERE id = ?", sets.join(", "));
    let mut query = sqlx::query(&sql);

    for b in &string_binds {
        query = query.bind(b);
    }
    if has_must_change {
        query = query.bind(payload.must_change_password.unwrap());
    }
    let _ = has_password; // consumed above via string_binds
    query = query.bind(id);

    match query.execute(pool).await {
        Ok(result) => {
            if result.rows_affected() == 0 {
                return Json(ApiResponse::error("User not found".to_string()));
            }
            // Fetch updated user
            let row = sqlx::query(
                "SELECT id, username, display_name, role, must_change_password, created_at FROM users WHERE id = ?",
            )
            .bind(id)
            .fetch_one(pool)
            .await;

            match row {
                Ok(row) => {
                    let uname: String = row.get("username");
                    let display_name: Option<String> = row.get("display_name");
                    Json(ApiResponse::success(serde_json::json!({
                        "id": row.get::<i64, _>("id"),
                        "username": &uname,
                        "display_name": display_name.as_deref().unwrap_or(&uname),
                        "role": row.get::<String, _>("role"),
                        "must_change_password": row.get::<bool, _>("must_change_password"),
                        "created_at": row.get::<chrono::DateTime<chrono::Utc>, _>("created_at"),
                    })))
                }
                Err(_) => Json(ApiResponse::success(serde_json::json!({ "message": "User updated" }))),
            }
        }
        Err(e) => {
            warn!("Failed to update user: {}", e);
            Json(ApiResponse::error(format!("Failed to update user: {}", e)))
        }
    }
}

pub(super) async fn handle_delete_user(
    State(state): State<DashboardState>,
    headers: axum::http::HeaderMap,
    Path(id): Path<i64>,
) -> Json<ApiResponse<serde_json::Value>> {
    let session = match auth::require_admin(&state, &headers).await {
        Ok(s) => s,
        Err(e) => return e,
    };

    let pool = &state.app_state.db_pool;

    // Don't allow deleting yourself
    let target_username: Option<String> =
        sqlx::query_scalar::<_, String>("SELECT username FROM users WHERE id = ?")
            .bind(id)
            .fetch_optional(pool)
            .await
            .unwrap_or(None);

    match target_username {
        None => return Json(ApiResponse::error("User not found".to_string())),
        Some(ref uname) if uname == &session.username => {
            return Json(ApiResponse::error("Cannot delete your own account".to_string()));
        }
        _ => {}
    }

    let result = sqlx::query("DELETE FROM users WHERE id = ?")
        .bind(id)
        .execute(pool)
        .await;

    match result {
        Ok(_) => Json(ApiResponse::success(serde_json::json!({ "message": "User deleted" }))),
        Err(e) => {
            warn!("Failed to delete user: {}", e);
            Json(ApiResponse::error(format!("Failed to delete user: {}", e)))
        }
    }
}
