//! Authentication API handlers.

use axum::{Json, extract::State};
use sqlx::PgPool;
use tower_sessions::Session;

use chattyness_db::{
    models::{
        AccountStatus, AgeCategory, AuthenticatedUser, CurrentUserResponse, GenderPreference,
        GuestLoginRequest, GuestLoginResponse, JoinRealmRequest, JoinRealmResponse, LoginRequest,
        LoginResponse, LoginType, PasswordResetRequest, PasswordResetResponse, RealmRole,
        RealmSummary, RegisterGuestRequest, RegisterGuestResponse, SignupRequest, SignupResponse,
        UserSummary,
    },
    queries::{memberships, realms, users},
};
use chattyness_error::AppError;
use chattyness_shared::{AgeConfig, GenderConfig, SignupConfig};

use crate::auth::{
    AuthUser, OptionalAuthUser,
    session::{
        SESSION_CURRENT_REALM_KEY, SESSION_LOGIN_TYPE_KEY, SESSION_ORIGINAL_DEST_KEY,
        SESSION_USER_ID_KEY,
    },
};

/// Get current user info.
pub async fn get_current_user(
    State(pool): State<PgPool>,
    OptionalAuthUser(user): OptionalAuthUser,
) -> Result<Json<CurrentUserResponse>, AppError> {
    match user {
        Some(user) => {
            // Get staff role if any
            let staff_role = memberships::get_user_staff_role(&pool, user.id).await?;

            Ok(Json(CurrentUserResponse {
                user: Some(AuthenticatedUser {
                    id: user.id,
                    username: user.username,
                    display_name: user.display_name,
                    avatar_url: user.avatar_url,
                    staff_role,
                }),
            }))
        }
        None => Ok(Json(CurrentUserResponse { user: None })),
    }
}

/// Login handler.
pub async fn login(
    rls_conn: crate::auth::RlsConn,
    State(pool): State<PgPool>,
    session: Session,
    Json(req): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, AppError> {
    // Validate the request
    req.validate()?;

    // Verify credentials
    let user = users::verify_password_with_reset_flag(&pool, &req.username, &req.password)
        .await?
        .ok_or(AppError::InvalidCredentials)?;

    // Set RLS context to the authenticated user for subsequent operations
    rls_conn
        .set_user_id(user.id)
        .await
        .map_err(|e| AppError::Internal(format!("Failed to set RLS context: {}", e)))?;

    // Check account status
    if user.status != AccountStatus::Active {
        return Err(AppError::AccountSuspended);
    }

    // Create user summary for response
    let user_summary = UserSummary {
        id: user.id,
        username: user.username.clone(),
        display_name: user.display_name.clone(),
        avatar_url: user.avatar_url.clone(),
    };

    // Handle based on login type
    match req.login_type {
        LoginType::Staff => {
            // Verify user is a staff member
            let staff_role = memberships::get_user_staff_role(&pool, user.id)
                .await?
                .ok_or(AppError::NotStaffMember)?;

            // Store session data
            session
                .insert(SESSION_USER_ID_KEY, user.id)
                .await
                .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
            session
                .insert(SESSION_LOGIN_TYPE_KEY, "staff")
                .await
                .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

            // Check for forced password reset
            if user.force_pw_reset {
                session
                    .insert(SESSION_ORIGINAL_DEST_KEY, "/staff")
                    .await
                    .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

                return Ok(Json(LoginResponse {
                    user: user_summary,
                    redirect_url: "/password-reset".to_string(),
                    requires_pw_reset: true,
                    is_member: None,
                    original_destination: Some("/staff".to_string()),
                    staff_role: Some(staff_role),
                    realm: None,
                }));
            }

            Ok(Json(LoginResponse {
                user: user_summary,
                redirect_url: "/staff".to_string(),
                requires_pw_reset: false,
                is_member: None,
                original_destination: None,
                staff_role: Some(staff_role),
                realm: None,
            }))
        }
        LoginType::Realm => {
            let realm_slug = req.realm_slug.as_ref().ok_or_else(|| {
                AppError::Validation("Realm slug is required for realm login".to_string())
            })?;

            // Get the realm
            let realm = realms::get_realm_by_slug(&pool, realm_slug)
                .await?
                .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", realm_slug)))?;

            // Check if user is a member
            let is_member = memberships::is_member(&pool, user.id, realm.id).await?;

            // Store session data
            session
                .insert(SESSION_USER_ID_KEY, user.id)
                .await
                .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
            session
                .insert(SESSION_LOGIN_TYPE_KEY, "realm")
                .await
                .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
            session
                .insert(SESSION_CURRENT_REALM_KEY, realm.id)
                .await
                .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

            let redirect_url = format!("/realms/{}", realm.slug);

            // Check for forced password reset
            if user.force_pw_reset {
                session
                    .insert(SESSION_ORIGINAL_DEST_KEY, redirect_url.clone())
                    .await
                    .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

                return Ok(Json(LoginResponse {
                    user: user_summary,
                    redirect_url: "/password-reset".to_string(),
                    requires_pw_reset: true,
                    is_member: Some(is_member),
                    original_destination: Some(redirect_url),
                    staff_role: None,
                    realm: Some(RealmSummary {
                        id: realm.id,
                        name: realm.name,
                        slug: realm.slug,
                        tagline: realm.tagline,
                        privacy: realm.privacy,
                        is_nsfw: realm.is_nsfw,
                        thumbnail_path: realm.thumbnail_path,
                        member_count: realm.member_count,
                        current_user_count: realm.current_user_count,
                    }),
                }));
            }

            // If not a member, include realm info for join confirmation
            if !is_member {
                return Ok(Json(LoginResponse {
                    user: user_summary,
                    redirect_url: redirect_url.clone(),
                    requires_pw_reset: false,
                    is_member: Some(false),
                    original_destination: None,
                    staff_role: None,
                    realm: Some(RealmSummary {
                        id: realm.id,
                        name: realm.name,
                        slug: realm.slug,
                        tagline: realm.tagline,
                        privacy: realm.privacy,
                        is_nsfw: realm.is_nsfw,
                        thumbnail_path: realm.thumbnail_path,
                        member_count: realm.member_count,
                        current_user_count: realm.current_user_count,
                    }),
                }));
            }

            // User is a member, update last visited (using RLS connection)
            let mut conn = rls_conn.acquire().await;
            memberships::update_last_visited_conn(&mut *conn, user.id, realm.id).await?;

            Ok(Json(LoginResponse {
                user: user_summary,
                redirect_url,
                requires_pw_reset: false,
                is_member: Some(true),
                original_destination: None,
                staff_role: None,
                realm: None,
            }))
        }
    }
}

/// Logout response.
#[derive(Debug, serde::Serialize)]
pub struct LogoutResponse {
    pub success: bool,
    pub redirect_url: String,
}

/// Logout handler.
pub async fn logout(session: Session) -> Result<Json<LogoutResponse>, AppError> {
    // Flush the session (removes all data and invalidates the session)
    session
        .flush()
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    Ok(Json(LogoutResponse {
        success: true,
        redirect_url: "/".to_string(),
    }))
}

/// Signup handler.
pub async fn signup(
    rls_conn: crate::auth::RlsConn,
    State(pool): State<PgPool>,
    State(signup_config): State<SignupConfig>,
    session: Session,
    Json(req): Json<SignupRequest>,
) -> Result<Json<SignupResponse>, AppError> {
    // Validate the request
    req.validate()?;

    // Check username availability (can use pool for read-only checks)
    if users::username_exists(&pool, &req.username).await? {
        return Err(AppError::Conflict("Username already taken".to_string()));
    }

    // Check email availability if provided
    if let Some(ref email) = req.email {
        let email_trimmed = email.trim();
        if !email_trimmed.is_empty() && users::email_exists(&pool, email_trimmed).await? {
            return Err(AppError::Conflict("Email already registered".to_string()));
        }
    }

    // Get the realm
    let realm = realms::get_realm_by_slug(&pool, &req.realm_slug)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", req.realm_slug)))?;

    // Determine gender preference based on config
    let gender_preference = match signup_config.gender {
        GenderConfig::Ask => req.gender_preference,
        GenderConfig::DefaultNeutral => Some(GenderPreference::GenderNeutral),
        GenderConfig::DefaultMale => Some(GenderPreference::GenderMale),
        GenderConfig::DefaultFemale => Some(GenderPreference::GenderFemale),
    };

    // Determine age category based on config
    let age_category = match signup_config.age {
        AgeConfig::Ask => req.age_category,
        AgeConfig::Infer => {
            // Infer age from birthday if provided
            if let Some(birthday) = req.birthday {
                Some(infer_age_category_from_birthday(birthday))
            } else {
                Some(AgeCategory::Adult) // Default to adult if no birthday
            }
        }
        AgeConfig::DefaultAdult => Some(AgeCategory::Adult),
        AgeConfig::DefaultChild => Some(AgeCategory::Child),
    };

    // Create the user using RLS connection
    let email_opt = req.email.as_ref().and_then(|e| {
        let trimmed = e.trim();
        if trimmed.is_empty() {
            None
        } else {
            Some(trimmed)
        }
    });

    let mut conn = rls_conn.acquire().await;
    let user_id = users::create_user_with_preferences_conn(
        &mut *conn,
        &req.username,
        email_opt,
        req.display_name.trim(),
        &req.password,
        req.birthday,
        gender_preference,
        age_category,
    )
    .await?;
    drop(conn);

    // Set RLS context to the new user for membership creation
    rls_conn
        .set_user_id(user_id)
        .await
        .map_err(|e| AppError::Internal(format!("Failed to set RLS context: {}", e)))?;

    // Create membership using RLS connection (now has user context)
    let mut conn = rls_conn.acquire().await;
    let membership_id =
        memberships::create_membership_conn(&mut *conn, user_id, realm.id, RealmRole::Member)
            .await?;

    // Set up session (user is logged in)
    session
        .insert(SESSION_USER_ID_KEY, user_id)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
    session
        .insert(SESSION_LOGIN_TYPE_KEY, "realm")
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
    session
        .insert(SESSION_CURRENT_REALM_KEY, realm.id)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    let redirect_url = format!("/realms/{}", realm.slug);

    Ok(Json(SignupResponse {
        user: UserSummary {
            id: user_id,
            username: req.username,
            display_name: req.display_name.trim().to_string(),
            avatar_url: None,
        },
        redirect_url,
        membership_id,
    }))
}

/// Guest login handler.
///
/// Creates a real user account with the 'guest' tag. Guests are regular users
/// with limited capabilities (no prop pickup, etc.) that can be reaped after 24 hours.
pub async fn guest_login(
    rls_conn: crate::auth::RlsConn,
    State(pool): State<PgPool>,
    session: Session,
    Json(req): Json<GuestLoginRequest>,
) -> Result<Json<GuestLoginResponse>, AppError> {
    // Validate the request
    req.validate()?;

    // Get the realm
    let realm = realms::get_realm_by_slug(&pool, &req.realm_slug)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", req.realm_slug)))?;

    // Check if realm allows guest access
    if !realm.allow_guest_access {
        return Err(AppError::Forbidden(
            "This realm does not allow guest access".to_string(),
        ));
    }

    // Generate guest name
    let guest_name = users::generate_guest_name();

    // Create guest user (no password) - trigger creates avatar automatically
    let user_id = users::create_guest_user(&pool, &guest_name).await?;

    // Set RLS context to the new guest user for membership creation
    rls_conn
        .set_user_id(user_id)
        .await
        .map_err(|e| AppError::Internal(format!("Failed to set RLS context: {}", e)))?;

    // Create membership for the guest so their position can be persisted
    let mut conn = rls_conn.acquire().await;
    memberships::create_membership_conn(&mut *conn, user_id, realm.id, RealmRole::Member).await?;
    drop(conn);

    // Set up tower session (same as regular user login)
    session
        .insert(SESSION_USER_ID_KEY, user_id)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
    session
        .insert(SESSION_LOGIN_TYPE_KEY, "guest")
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;
    session
        .insert(SESSION_CURRENT_REALM_KEY, realm.id)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    let redirect_url = format!("/realms/{}", realm.slug);

    Ok(Json(GuestLoginResponse {
        guest_name,
        user_id,
        redirect_url,
        realm: RealmSummary {
            id: realm.id,
            name: realm.name,
            slug: realm.slug,
            tagline: realm.tagline,
            privacy: realm.privacy,
            is_nsfw: realm.is_nsfw,
            thumbnail_path: realm.thumbnail_path,
            member_count: realm.member_count,
            current_user_count: realm.current_user_count,
        },
    }))
}

/// Join realm handler.
pub async fn join_realm(
    rls_conn: crate::auth::RlsConn,
    State(pool): State<PgPool>,
    AuthUser(user): AuthUser,
    Json(req): Json<JoinRealmRequest>,
) -> Result<Json<JoinRealmResponse>, AppError> {
    // Get the realm to verify it exists and check privacy
    let realm = realms::get_realm_by_id(&pool, req.realm_id)
        .await?
        .ok_or_else(|| AppError::NotFound("Realm not found".to_string()))?;

    // Check if user is already a member
    let is_member = memberships::is_member(&pool, user.id, realm.id).await?;
    if is_member {
        return Err(AppError::Conflict(
            "Already a member of this realm".to_string(),
        ));
    }

    // For private realms, don't allow direct join (would need invitation)
    if realm.privacy == chattyness_db::models::RealmPrivacy::Private {
        return Err(AppError::Forbidden(
            "Cannot join private realms without an invitation".to_string(),
        ));
    }

    // Create the membership using RLS connection (policy requires user_id = current_user_id)
    let mut conn = rls_conn.acquire().await;
    let membership_id =
        memberships::create_membership_conn(&mut *conn, user.id, realm.id, RealmRole::Member)
            .await?;

    Ok(Json(JoinRealmResponse {
        success: true,
        membership_id,
        redirect_url: format!("/realms/{}", realm.slug),
    }))
}

/// Password reset handler.
pub async fn reset_password(
    rls_conn: crate::auth::RlsConn,
    session: Session,
    AuthUser(user): AuthUser,
    Json(req): Json<PasswordResetRequest>,
) -> Result<Json<PasswordResetResponse>, AppError> {
    // Validate the request
    req.validate()?;

    // Update the password using RLS connection (required for RLS policy)
    let mut conn = rls_conn.acquire().await;
    users::update_password_conn(&mut *conn, user.id, &req.new_password).await?;

    // Get the original destination from session
    let original_dest: Option<String> = session
        .get(SESSION_ORIGINAL_DEST_KEY)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    // Clear the original destination from session
    session
        .remove::<String>(SESSION_ORIGINAL_DEST_KEY)
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    let redirect_url = original_dest.unwrap_or_else(|| "/".to_string());

    Ok(Json(PasswordResetResponse {
        success: true,
        redirect_url,
    }))
}

/// Register guest handler.
///
/// Upgrades a guest user to a full user account with username and password.
pub async fn register_guest(
    rls_conn: crate::auth::RlsConn,
    State(pool): State<PgPool>,
    session: Session,
    AuthUser(user): AuthUser,
    Json(req): Json<RegisterGuestRequest>,
) -> Result<Json<RegisterGuestResponse>, AppError> {
    // Validate the request
    req.validate()?;

    // Verify user is a guest
    if !user.is_guest() {
        return Err(AppError::Forbidden(
            "Only guests can register for an account".to_string(),
        ));
    }

    // Check username availability
    if users::username_exists(&pool, &req.username).await? {
        return Err(AppError::Conflict("Username already taken".to_string()));
    }

    // Check email availability if provided
    if let Some(ref email) = req.email {
        let email_trimmed = email.trim();
        if !email_trimmed.is_empty() && users::email_exists(&pool, email_trimmed).await? {
            return Err(AppError::Conflict("Email already registered".to_string()));
        }
    }

    // Get email as Option<&str>
    let email_opt = req.email.as_ref().and_then(|e| {
        let trimmed = e.trim();
        if trimmed.is_empty() {
            None
        } else {
            Some(trimmed)
        }
    });

    // Upgrade the guest to a full user using RLS connection
    let mut conn = rls_conn.acquire().await;
    users::upgrade_guest_to_user_conn(&mut *conn, user.id, &req.username, &req.password, email_opt).await?;

    // Update session login type from 'guest' to 'realm'
    session
        .insert(SESSION_LOGIN_TYPE_KEY, "realm")
        .await
        .map_err(|e| AppError::Internal(format!("Session error: {}", e)))?;

    Ok(Json(RegisterGuestResponse {
        success: true,
        username: req.username,
    }))
}

/// Request to update user preferences.
#[derive(Debug, serde::Deserialize)]
pub struct UpdatePreferencesRequest {
    #[serde(default)]
    pub birthday: Option<chrono::NaiveDate>,
    #[serde(default)]
    pub gender_preference: Option<chattyness_db::models::GenderPreference>,
    #[serde(default)]
    pub age_category: Option<chattyness_db::models::AgeCategory>,
}

/// Response after updating preferences.
#[derive(Debug, serde::Serialize)]
pub struct UpdatePreferencesResponse {
    pub success: bool,
}

/// Update user preferences handler.
///
/// Updates the user's birthday, gender preference, and/or age category.
/// These preferences are used for default avatar selection.
pub async fn update_preferences(
    rls_conn: crate::auth::RlsConn,
    AuthUser(user): AuthUser,
    Json(req): Json<UpdatePreferencesRequest>,
) -> Result<Json<UpdatePreferencesResponse>, AppError> {
    let mut conn = rls_conn.acquire().await;

    // Update user preferences (requires RLS for auth.users UPDATE policy)
    users::update_user_preferences_conn(
        &mut *conn,
        user.id,
        req.birthday,
        req.gender_preference,
        req.age_category,
    )
    .await?;

    Ok(Json(UpdatePreferencesResponse { success: true }))
}

/// Infer age category from birthday.
///
/// Returns `Adult` if 18 years or older, `Child` otherwise.
fn infer_age_category_from_birthday(birthday: chrono::NaiveDate) -> AgeCategory {
    use chrono::Datelike;
    let today = chrono::Utc::now().date_naive();
    let age = today.year() - birthday.year();
    let had_birthday_this_year =
        (today.month(), today.day()) >= (birthday.month(), birthday.day());
    let actual_age = if had_birthday_this_year {
        age
    } else {
        age - 1
    };
    if actual_age >= 18 {
        AgeCategory::Adult
    } else {
        AgeCategory::Child
    }
}
