//! User management database queries.
//!
//! These queries are used by the owner interface and require the chattyness_owner role.

use sqlx::PgPool;
use uuid::Uuid;

use crate::models::{
    AccountStatus, CreateUserRequest, NewUserData, RealmRole, RealmSummary, UserDetail,
    UserListItem, UserRealmMembership,
};
use chattyness_error::AppError;

use super::helpers::{generate_random_token, hash_password};

/// List all users with pagination.
pub async fn list_users(
    pool: &PgPool,
    limit: i64,
    offset: i64,
) -> Result<Vec<UserListItem>, AppError> {
    let users = sqlx::query_as::<_, UserListItem>(
        r#"
        SELECT
            u.id,
            u.username,
            u.display_name,
            u.email,
            u.status,
            u.reputation_tier,
            s.role as staff_role,
            u.created_at,
            u.last_seen_at
        FROM auth.users u
        LEFT JOIN server.staff s ON u.id = s.user_id
        ORDER BY u.created_at DESC
        LIMIT $1 OFFSET $2
        "#,
    )
    .bind(limit)
    .bind(offset)
    .fetch_all(pool)
    .await?;

    Ok(users)
}

/// Search users by username, email, or display_name.
pub async fn search_users(
    pool: &PgPool,
    query: &str,
    limit: i64,
) -> Result<Vec<UserListItem>, AppError> {
    let pattern = format!("%{}%", query);
    let users = sqlx::query_as::<_, UserListItem>(
        r#"
        SELECT
            u.id,
            u.username,
            u.display_name,
            u.email,
            u.status,
            u.reputation_tier,
            s.role as staff_role,
            u.created_at,
            u.last_seen_at
        FROM auth.users u
        LEFT JOIN server.staff s ON u.id = s.user_id
        WHERE
            u.username ILIKE $1
            OR u.email ILIKE $1
            OR u.display_name ILIKE $1
        ORDER BY
            CASE
                WHEN u.username ILIKE $1 THEN 1
                WHEN u.display_name ILIKE $1 THEN 2
                ELSE 3
            END,
            u.username
        LIMIT $2
        "#,
    )
    .bind(&pattern)
    .bind(limit)
    .fetch_all(pool)
    .await?;

    Ok(users)
}

/// Get full user detail by ID.
pub async fn get_user_detail(pool: &PgPool, user_id: Uuid) -> Result<UserDetail, AppError> {
    let user = sqlx::query_as::<_, UserDetail>(
        r#"
        SELECT
            u.id,
            u.username,
            u.email,
            u.display_name,
            u.bio,
            u.avatar_url,
            u.reputation_tier,
            u.status,
            u.email_verified,
            s.role as staff_role,
            u.created_at,
            u.updated_at,
            u.last_seen_at
        FROM auth.users u
        LEFT JOIN server.staff s ON u.id = s.user_id
        WHERE u.id = $1
        "#,
    )
    .bind(user_id)
    .fetch_one(pool)
    .await?;

    Ok(user)
}

/// Update a user's account status.
pub async fn update_user_status(
    pool: &PgPool,
    user_id: Uuid,
    status: AccountStatus,
) -> Result<UserDetail, AppError> {
    // First update the status
    sqlx::query(
        r#"
        UPDATE auth.users
        SET status = $1, updated_at = now()
        WHERE id = $2
        "#,
    )
    .bind(status)
    .bind(user_id)
    .execute(pool)
    .await?;

    // Then return the updated user detail
    get_user_detail(pool, user_id).await
}

/// Create a new user account with a random temporary password.
/// Returns (user_id, plaintext_token) - the token should be shown to the server owner.
pub async fn create_user(pool: &PgPool, data: &NewUserData) -> Result<(Uuid, String), AppError> {
    // Generate a random token as the temporary password
    let token = generate_random_token();
    let password_hash = hash_password(&token)?;

    let user_id = sqlx::query_scalar::<_, Uuid>(
        r#"
        INSERT INTO auth.users (username, email, display_name, password_hash, force_pw_reset)
        VALUES ($1, $2, $3, $4, true)
        RETURNING id
        "#,
    )
    .bind(&data.username)
    .bind(&data.email)
    .bind(&data.display_name)
    .bind(&password_hash)
    .fetch_one(pool)
    .await?;

    Ok((user_id, token))
}

/// Reset a user's password to a random token.
/// Returns the plaintext token (to show to the server owner).
pub async fn reset_user_password(pool: &PgPool, user_id: Uuid) -> Result<String, AppError> {
    let token = generate_random_token();
    let password_hash = hash_password(&token)?;

    sqlx::query(
        r#"
        UPDATE auth.users
        SET password_hash = $1, force_pw_reset = true, updated_at = now()
        WHERE id = $2
        "#,
    )
    .bind(&password_hash)
    .bind(user_id)
    .execute(pool)
    .await?;

    Ok(token)
}

/// Get all realm memberships for a user.
pub async fn get_user_realms(
    pool: &PgPool,
    user_id: Uuid,
) -> Result<Vec<UserRealmMembership>, AppError> {
    let memberships = sqlx::query_as::<_, UserRealmMembership>(
        r#"
        SELECT
            m.realm_id,
            r.name as realm_name,
            r.slug as realm_slug,
            m.role,
            m.nickname,
            m.created_at as joined_at,
            m.last_visited_at
        FROM realm.memberships m
        JOIN realm.realms r ON m.realm_id = r.id
        WHERE m.user_id = $1
        ORDER BY m.last_visited_at DESC NULLS LAST, m.created_at DESC
        "#,
    )
    .bind(user_id)
    .fetch_all(pool)
    .await?;

    Ok(memberships)
}

/// Add a user to a realm.
pub async fn add_user_to_realm(
    pool: &PgPool,
    user_id: Uuid,
    realm_id: Uuid,
    role: RealmRole,
) -> Result<(), AppError> {
    sqlx::query(
        r#"
        INSERT INTO realm.memberships (realm_id, user_id, role)
        VALUES ($1, $2, $3)
        ON CONFLICT (realm_id, user_id) DO UPDATE SET role = $3
        "#,
    )
    .bind(realm_id)
    .bind(user_id)
    .bind(role)
    .execute(pool)
    .await?;

    Ok(())
}

/// Remove a user from a realm.
pub async fn remove_user_from_realm(
    pool: &PgPool,
    user_id: Uuid,
    realm_id: Uuid,
) -> Result<(), AppError> {
    sqlx::query(
        r#"
        DELETE FROM realm.memberships
        WHERE realm_id = $1 AND user_id = $2
        "#,
    )
    .bind(realm_id)
    .bind(user_id)
    .execute(pool)
    .await?;

    Ok(())
}

/// List all realms (for dropdown selection).
pub async fn list_all_realms(pool: &PgPool) -> Result<Vec<RealmSummary>, AppError> {
    let realms = sqlx::query_as::<_, RealmSummary>(
        r#"
        SELECT
            id,
            name,
            slug,
            tagline,
            privacy,
            is_nsfw,
            thumbnail_path,
            member_count,
            current_user_count
        FROM realm.realms
        ORDER BY name
        "#,
    )
    .fetch_all(pool)
    .await?;

    Ok(realms)
}

/// Create a new user with optional staff role (atomically).
/// Returns (user_id, plaintext_token) - the token should be shown to the server owner.
pub async fn create_user_with_staff(
    pool: &PgPool,
    req: &CreateUserRequest,
) -> Result<(Uuid, String), AppError> {
    // Generate a random token as the temporary password
    let token = generate_random_token();
    let password_hash = hash_password(&token)?;

    // Start a transaction if we need to also create staff record
    if let Some(staff_role) = req.staff_role {
        let mut tx = pool.begin().await?;

        // Create the user with force_pw_reset = true
        let user_id = sqlx::query_scalar::<_, Uuid>(
            r#"
            INSERT INTO auth.users (username, email, display_name, password_hash, force_pw_reset)
            VALUES ($1, $2, $3, $4, true)
            RETURNING id
            "#,
        )
        .bind(&req.username)
        .bind(&req.email)
        .bind(&req.display_name)
        .bind(&password_hash)
        .fetch_one(&mut *tx)
        .await?;

        // Create the staff record
        sqlx::query(
            r#"
            INSERT INTO server.staff (user_id, role)
            VALUES ($1, $2)
            "#,
        )
        .bind(user_id)
        .bind(staff_role)
        .execute(&mut *tx)
        .await?;

        tx.commit().await?;

        Ok((user_id, token))
    } else {
        // Just create the user with force_pw_reset = true
        let user_id = sqlx::query_scalar::<_, Uuid>(
            r#"
            INSERT INTO auth.users (username, email, display_name, password_hash, force_pw_reset)
            VALUES ($1, $2, $3, $4, true)
            RETURNING id
            "#,
        )
        .bind(&req.username)
        .bind(&req.email)
        .bind(&req.display_name)
        .bind(&password_hash)
        .fetch_one(pool)
        .await?;

        Ok((user_id, token))
    }
}
