//! Admin authentication API handlers.

use axum::{Json, extract::State, http::StatusCode};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use tower_sessions::Session;

use crate::auth::ADMIN_SESSION_STAFF_ID_KEY;

/// Login request body.
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

/// Login response body.
#[derive(Debug, Serialize)]
pub struct LoginResponse {
    pub success: bool,
    pub username: String,
    pub display_name: String,
}

/// Error response body.
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: String,
}

/// Staff member row for login lookup.
#[derive(Debug, sqlx::FromRow)]
struct StaffLoginRow {
    user_id: uuid::Uuid,
    username: String,
    display_name: String,
}

/// Login handler for server staff.
///
/// Authenticates staff member and creates a session.
pub async fn login(
    State(pool): State<PgPool>,
    session: Session,
    Json(request): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Look up the staff member
    let staff: Option<StaffLoginRow> = sqlx::query_as(
        r#"
        SELECT
            u.id as user_id,
            u.username,
            u.display_name
        FROM auth.users u
        JOIN server.staff s ON s.user_id = u.id
        WHERE u.username = $1
        "#,
    )
    .bind(&request.username)
    .fetch_optional(&pool)
    .await
    .map_err(|e| {
        tracing::error!("Database error during login: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Internal server error".to_string(),
            }),
        )
    })?;

    let staff = match staff {
        Some(s) => s,
        None => {
            return Err((
                StatusCode::UNAUTHORIZED,
                Json(ErrorResponse {
                    error: "Invalid username or password".to_string(),
                }),
            ));
        }
    };

    // Verify password using Argon2
    let password_hash: Option<String> = sqlx::query_scalar(
        r#"
        SELECT password_hash
        FROM auth.users
        WHERE id = $1
        "#,
    )
    .bind(staff.user_id)
    .fetch_one(&pool)
    .await
    .map_err(|e| {
        tracing::error!("Database error during password check: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Internal server error".to_string(),
            }),
        )
    })?;

    let password_hash = match password_hash {
        Some(h) => h,
        None => {
            return Err((
                StatusCode::UNAUTHORIZED,
                Json(ErrorResponse {
                    error: "Invalid username or password".to_string(),
                }),
            ));
        }
    };

    // Verify password with Argon2
    use argon2::{Argon2, PasswordHash, PasswordVerifier};
    let parsed_hash = PasswordHash::new(&password_hash).map_err(|e| {
        tracing::error!("Password hash parse error: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Internal server error".to_string(),
            }),
        )
    })?;

    if Argon2::default()
        .verify_password(request.password.as_bytes(), &parsed_hash)
        .is_err()
    {
        return Err((
            StatusCode::UNAUTHORIZED,
            Json(ErrorResponse {
                error: "Invalid username or password".to_string(),
            }),
        ));
    }

    // Check if user is suspended or banned
    let status: String = sqlx::query_scalar(r#"SELECT status::text FROM auth.users WHERE id = $1"#)
        .bind(staff.user_id)
        .fetch_one(&pool)
        .await
        .map_err(|e| {
            tracing::error!("Database error during status check: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Internal server error".to_string(),
                }),
            )
        })?;

    if status != "active" {
        return Err((
            StatusCode::FORBIDDEN,
            Json(ErrorResponse {
                error: format!("Account is {}", status),
            }),
        ));
    }

    // Create session
    session
        .insert(ADMIN_SESSION_STAFF_ID_KEY, staff.user_id)
        .await
        .map_err(|e| {
            tracing::error!("Session error: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Session error".to_string(),
                }),
            )
        })?;

    Ok(Json(LoginResponse {
        success: true,
        username: staff.username,
        display_name: staff.display_name,
    }))
}

/// Logout handler.
///
/// Clears the session.
pub async fn logout(session: Session) -> Result<Json<serde_json::Value>, StatusCode> {
    session.flush().await.map_err(|e| {
        tracing::error!("Session flush error: {}", e);
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    Ok(Json(serde_json::json!({ "success": true })))
}

// =============================================================================
// Auth Context Types (shared between SSR and hydrate)
// =============================================================================

/// Realm info for auth context.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ManagedRealm {
    pub slug: String,
    pub name: String,
}

/// Auth context response for the frontend.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AuthContextResponse {
    pub is_server_staff: bool,
    pub managed_realms: Vec<ManagedRealm>,
}

/// Get auth context endpoint.
///
/// Returns the current user's permissions for rendering the sidebar.
pub async fn get_auth_context(
    State(pool): State<PgPool>,
    session: Session,
) -> Result<Json<AuthContextResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Try to get staff_id from session (server staff)
    let staff_id: Option<uuid::Uuid> = session.get(ADMIN_SESSION_STAFF_ID_KEY).await.ok().flatten();

    if let Some(staff_id) = staff_id {
        // Check if this is actually a staff member
        let is_staff: Option<bool> =
            sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server.staff WHERE user_id = $1)")
                .bind(staff_id)
                .fetch_one(&pool)
                .await
                .ok();

        if is_staff == Some(true) {
            return Ok(Json(AuthContextResponse {
                is_server_staff: true,
                managed_realms: vec![],
            }));
        }
    }

    // Try to get user_id from session (realm admin)
    let user_id: Option<uuid::Uuid> = session
        .get(crate::auth::SESSION_USER_ID_KEY)
        .await
        .ok()
        .flatten();

    if let Some(user_id) = user_id {
        // Get realms where this user has admin privileges (owner, moderator, builder)
        let realms: Vec<ManagedRealm> = sqlx::query_as::<_, (String, String)>(
            r#"
            SELECT r.slug, r.name
            FROM realm.realms r
            JOIN realm.memberships m ON m.realm_id = r.id
            WHERE m.user_id = $1
            AND m.role IN ('owner', 'moderator', 'builder')
            ORDER BY r.name
            "#,
        )
        .bind(user_id)
        .fetch_all(&pool)
        .await
        .map_err(|e| {
            tracing::error!("Database error fetching managed realms: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Internal server error".to_string(),
                }),
            )
        })?
        .into_iter()
        .map(|(slug, name)| ManagedRealm { slug, name })
        .collect();

        return Ok(Json(AuthContextResponse {
            is_server_staff: false,
            managed_realms: realms,
        }));
    }

    // No valid session
    Err((
        StatusCode::UNAUTHORIZED,
        Json(ErrorResponse {
            error: "Not authenticated".to_string(),
        }),
    ))
}
