use argon2::{Argon2, password_hash::PasswordHasher};
use db::sqlx;
use model::users::UserModel;
use serde::{Deserialize, Serialize};
use session::Session;

use crate::{AppService, error::AppError};

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct RegisterParams {
    pub username: String,
    pub email: String,
    pub password: String,
    pub captcha: String,
}

impl AppService {
    pub async fn auth_register(
        &self,
        params: RegisterParams,
        context: &Session,
    ) -> Result<UserModel, AppError> {
        self.auth_check_captcha(context, params.captcha).await?;
        let password = self.auth_rsa_decode(context, params.password).await?;
        Self::validate_password_strength(&password)?;

        let username_exists = self
            .auth_find_user_by_username(&params.username)
            .await
            .is_ok();
        let email_exists =
            self.auth_find_user_by_email(&params.email).await.is_ok();
        if username_exists || email_exists {
            return Err(AppError::AccountAlreadyExists);
        }

        let user_id = uuid::Uuid::now_v7();
        let now = chrono::Utc::now();
        let password_hash = Argon2::default()
            .hash_password(password.as_bytes())
            .map_err(|e| AppError::PasswordHashError(e.to_string()))?
            .to_string();

        let mut txn = self.db.begin().await.map_err(|_| AppError::TxnError)?;

        let user = sqlx::query_as::<_, UserModel>(
            "INSERT INTO \"user\" \
             (id, username, display_name, avatar_url, website_url, allow_use, can_search, \
              last_sign_in_at, created_at, updated_at) \
             VALUES ($1, $2, $3, '', '', true, true, NULL, $4, $4) \
             RETURNING id, username, display_name, avatar_url, website_url, allow_use, can_search, \
                       last_sign_in_at, created_at, updated_at",
        )
        .bind(user_id)
        .bind(&params.username)
        .bind(&params.username)
        .bind(now)
        .fetch_one(&mut **txn.inner_mut())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        sqlx::query(
            "INSERT INTO user_email (\"user\", email, created_at, active, last_use_login, updated_at) \
             VALUES ($1, $2, $3, true, NULL, $3)",
        )
        .bind(user_id)
        .bind(&params.email)
        .bind(now)
        .execute(&mut **txn.inner_mut())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        sqlx::query(
            "INSERT INTO user_password (\"user\", hash, salt, is_active, reason, created_at, updated_at) \
             VALUES ($1, $2, '', true, NULL, $3, $3)",
        )
        .bind(user_id)
        .bind(&password_hash)
        .bind(now)
        .execute(&mut **txn.inner_mut())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        txn.commit().await.map_err(|_| AppError::TxnError)?;

        context.set_user(user_id);
        context.remove(Self::RSA_PRIVATE_KEY);
        context.remove(Self::RSA_PUBLIC_KEY);
        tracing::info!(user_uid = %user_id, username = %user.username, "User registered successfully");
        Ok(user)
    }
}
