use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::CollabKVDB;
use flowy_error::{internal_error, ErrorCode, FlowyResult};

use arc_swap::ArcSwapOption;
use collab::lock::RwLock;
use collab_user::core::UserAwareness;
use dashmap::DashMap;
use flowy_server_pub::AuthenticatorType;
use flowy_sqlite::kv::KVStorePreferences;
use flowy_sqlite::schema::user_table;
use flowy_sqlite::ConnectionPool;
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
use flowy_user_pub::cloud::{UserCloudServiceProvider, UserUpdate};
use flowy_user_pub::entities::*;
use flowy_user_pub::workspace_service::UserWorkspaceService;
use semver::Version;
use serde_json::Value;
use std::string::ToString;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Weak};
use tokio::sync::Mutex;
use tokio_stream::StreamExt;
use tracing::{debug, error, event, info, instrument, warn};

use lib_infra::box_any::BoxAny;

use crate::entities::{AuthStateChangedPB, AuthStatePB, UserProfilePB, UserSettingPB};
use crate::event_map::{DefaultUserStatusCallback, UserStatusCallback};
use crate::migrations::document_empty_content::HistoricalEmptyDocumentMigration;
use crate::migrations::migration::{
  save_migration_record, UserDataMigration, UserLocalDataMigration, FIRST_TIME_INSTALL_VERSION,
};
use crate::migrations::workspace_and_favorite_v1::FavoriteV1AndWorkspaceArrayMigration;
use crate::migrations::workspace_trash_v1::WorkspaceTrashMapToSectionMigration;
use crate::migrations::AnonUser;
use crate::services::authenticate_user::AuthenticateUser;
use crate::services::cloud_config::get_cloud_config;
use crate::services::collab_interact::{DefaultCollabInteract, UserReminder};

use super::manager_user_workspace::save_user_workspace;
use crate::migrations::doc_key_with_workspace::CollabDocKeyWithWorkspaceIdMigration;
use crate::services::sqlite_sql::user_sql::{select_user_profile, UserTable, UserTableChangeset};
use crate::user_manager::manager_user_encryption::validate_encryption_sign;
use crate::user_manager::manager_user_workspace::save_all_user_workspaces;
use crate::user_manager::user_login_state::UserAuthProcess;
use crate::{errors::FlowyError, notification::*};
use flowy_user_pub::session::Session;

pub struct UserManager {
  pub(crate) cloud_services: Arc<dyn UserCloudServiceProvider>,
  pub(crate) store_preferences: Arc<KVStorePreferences>,
  pub(crate) user_awareness: Arc<ArcSwapOption<RwLock<UserAwareness>>>,
  pub(crate) user_status_callback: RwLock<Arc<dyn UserStatusCallback>>,
  pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>,
  pub(crate) collab_interact: RwLock<Arc<dyn UserReminder>>,
  pub(crate) user_workspace_service: Arc<dyn UserWorkspaceService>,
  auth_process: Mutex<Option<UserAuthProcess>>,
  pub(crate) authenticate_user: Arc<AuthenticateUser>,
  refresh_user_profile_since: AtomicI64,
  pub(crate) is_loading_awareness: Arc<DashMap<String, bool>>,
}

impl UserManager {
  pub fn new(
    cloud_services: Arc<dyn UserCloudServiceProvider>,
    store_preferences: Arc<KVStorePreferences>,
    collab_builder: Weak<AppFlowyCollabBuilder>,
    authenticate_user: Arc<AuthenticateUser>,
    user_workspace_service: Arc<dyn UserWorkspaceService>,
  ) -> Arc<Self> {
    let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> =
      RwLock::new(Arc::new(DefaultUserStatusCallback));

    let refresh_user_profile_since = AtomicI64::new(0);
    let user_manager = Arc::new(Self {
      cloud_services,
      store_preferences,
      user_awareness: Default::default(),
      user_status_callback,
      collab_builder,
      collab_interact: RwLock::new(Arc::new(DefaultCollabInteract)),
      auth_process: Default::default(),
      authenticate_user,
      refresh_user_profile_since,
      user_workspace_service,
      is_loading_awareness: Arc::new(Default::default()),
    });

    let weak_user_manager = Arc::downgrade(&user_manager);
    if let Ok(user_service) = user_manager.cloud_services.get_user_service() {
      if let Some(mut rx) = user_service.subscribe_user_update() {
        tokio::spawn(async move {
          while let Some(update) = rx.recv().await {
            if let Some(user_manager) = weak_user_manager.upgrade() {
              if let Err(err) = user_manager.handler_user_update(update).await {
                error!("handler_user_update failed: {:?}", err);
              }
            }
          }
        });
      }
    }

    user_manager
  }

  pub fn close_db(&self) {
    if let Err(err) = self.authenticate_user.close_db() {
      error!("Close db failed: {:?}", err);
    }
  }

  pub fn get_store_preferences(&self) -> Weak<KVStorePreferences> {
    Arc::downgrade(&self.store_preferences)
  }

  /// Initializes the user session, including data migrations and user awareness configuration. This function
  /// will be invoked each time the user opens the application.
  ///
  /// Starts by retrieving the current session. If the session is successfully obtained, it will attempt
  /// a local data migration for the user. After ensuring the user's data is migrated and up-to-date,
  /// the function will set up the collaboration configuration and initialize the user's awareness. Upon successful
  /// completion, a user status callback is invoked to signify that the initialization process is complete.
  #[instrument(level = "debug", skip_all, err)]
  pub async fn init_with_callback<C: UserStatusCallback + 'static, I: UserReminder>(
    &self,
    user_status_callback: C,
    collab_interact: I,
  ) -> Result<(), FlowyError> {
    let user_status_callback = Arc::new(user_status_callback);
    *self.user_status_callback.write().await = user_status_callback.clone();
    *self.collab_interact.write().await = Arc::new(collab_interact);

    if let Ok(session) = self.get_session() {
      let user = self.get_user_profile_from_disk(session.user_id).await?;

      // Get the current authenticator from the environment variable
      let current_authenticator = current_authenticator();

      // If the current authenticator is different from the authenticator in the session and it's
      // not a local authenticator, we need to sign out the user.
      if user.authenticator != Authenticator::Local && user.authenticator != current_authenticator {
        event!(
          tracing::Level::INFO,
          "Authenticator changed from {:?} to {:?}",
          user.authenticator,
          current_authenticator
        );
        self.sign_out().await?;
        return Ok(());
      }

      event!(
        tracing::Level::INFO,
        "init user session: {}:{}, authenticator: {:?}",
        user.uid,
        user.email,
        user.authenticator,
      );

      self.prepare_user(&session).await;
      self.prepare_backup(&session).await;

      // Set the token if the current cloud service using token to authenticate
      // Currently, only the AppFlowy cloud using token to init the client api.
      // TODO(nathan): using trait to separate the init process for different cloud service
      if user.authenticator.is_appflowy_cloud() {
        if let Err(err) = self.cloud_services.set_token(&user.token) {
          error!("Set token failed: {}", err);
        }

        if let Err(err) = self.cloud_services.set_ai_model(&user.ai_model) {
          error!("Set ai model failed: {}", err);
        }

        // Subscribe the token state
        let weak_cloud_services = Arc::downgrade(&self.cloud_services);
        let weak_authenticate_user = Arc::downgrade(&self.authenticate_user);
        let weak_pool = Arc::downgrade(&self.db_pool(user.uid)?);
        let cloned_session = session.clone();
        if let Some(mut token_state_rx) = self.cloud_services.subscribe_token_state() {
          event!(tracing::Level::DEBUG, "Listen token state change");
          let user_uid = user.uid;
          let local_token = user.token.clone();
          tokio::spawn(async move {
            while let Some(token_state) = token_state_rx.next().await {
              debug!("Token state changed: {:?}", token_state);
              match token_state {
                UserTokenState::Refresh { token: new_token } => {
                  // Only save the token if the token is different from the current token
                  if new_token != local_token {
                    if let Some(conn) = weak_pool.upgrade().and_then(|pool| pool.get().ok()) {
                      // Save the new token
                      if let Err(err) = save_user_token(user_uid, conn, new_token) {
                        error!("Save user token failed: {}", err);
                      }
                    }
                  }
                },
                UserTokenState::Invalid => {
                  // Attempt to upgrade the weak reference for cloud_services
                  let cloud_services = match weak_cloud_services.upgrade() {
                    Some(cloud_services) => cloud_services,
                    None => {
                      error!("Failed to upgrade weak reference for cloud_services");
                      return; // Exit early if the upgrade fails
                    },
                  };

                  // Attempt to upgrade the weak reference for authenticate_user
                  let authenticate_user = match weak_authenticate_user.upgrade() {
                    Some(authenticate_user) => authenticate_user,
                    None => {
                      warn!("Failed to upgrade weak reference for authenticate_user");
                      return; // Exit early if the upgrade fails
                    },
                  };

                  // Attempt to upgrade the weak reference for pool and then get a connection
                  let conn = match weak_pool.upgrade() {
                    Some(pool) => match pool.get() {
                      Ok(conn) => conn,
                      Err(_) => {
                        warn!("Failed to get connection from pool");
                        return; // Exit early if getting connection fails
                      },
                    },
                    None => {
                      warn!("Failed to upgrade weak reference for pool");
                      return; // Exit early if the upgrade fails
                    },
                  };

                  // If all upgrades succeed, proceed with the sign_out operation
                  if let Err(err) =
                    sign_out(&cloud_services, &cloned_session, &authenticate_user, conn).await
                  {
                    error!("Sign out when token invalid failed: {:?}", err);
                  }
                },
                UserTokenState::Init => {},
              }
            }
          });
        }
      }

      // Do the user data migration if needed.
      event!(tracing::Level::INFO, "Prepare user data migration");
      match (
        self
          .authenticate_user
          .database
          .get_collab_db(session.user_id),
        self.authenticate_user.database.get_pool(session.user_id),
      ) {
        (Ok(collab_db), Ok(sqlite_pool)) => {
          run_collab_data_migration(
            &session,
            &user,
            collab_db,
            sqlite_pool,
            self.store_preferences.clone(),
            &self.authenticate_user.user_config.app_version,
          );
        },
        _ => error!("Failed to get collab db or sqlite pool"),
      }
      self.authenticate_user.vacuum_database_if_need();

      // migrations should run before set the first time installed version
      self.set_first_time_installed_version();
      let cloud_config = get_cloud_config(session.user_id, &self.store_preferences);
      // Init the user awareness. here we ignore the error
      let _ = self
        .initial_user_awareness(&session, &user.authenticator)
        .await;

      user_status_callback
        .did_init(
          user.uid,
          &user.authenticator,
          &cloud_config,
          &session.user_workspace,
          &self.authenticate_user.user_config.device_id,
          &user.authenticator,
        )
        .await?;
    } else {
      self.set_first_time_installed_version();
    }
    Ok(())
  }

  fn set_first_time_installed_version(&self) {
    if self
      .store_preferences
      .get_str(FIRST_TIME_INSTALL_VERSION)
      .is_none()
    {
      info!(
        "Set install version: {:?}",
        self.authenticate_user.user_config.app_version
      );
      if let Err(err) = self.store_preferences.set_object(
        FIRST_TIME_INSTALL_VERSION,
        &self.authenticate_user.user_config.app_version,
      ) {
        error!("Set install version error: {:?}", err);
      }
    }
  }

  pub fn get_session(&self) -> FlowyResult<Arc<Session>> {
    self.authenticate_user.get_session()
  }

  pub fn db_connection(&self, uid: i64) -> Result<DBConnection, FlowyError> {
    self.authenticate_user.database.get_connection(uid)
  }

  pub fn db_pool(&self, uid: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
    self.authenticate_user.database.get_pool(uid)
  }

  pub fn get_collab_db(&self, uid: i64) -> Result<Weak<CollabKVDB>, FlowyError> {
    self
      .authenticate_user
      .database
      .get_collab_db(uid)
      .map(|collab_db| Arc::downgrade(&collab_db))
  }

  #[cfg(debug_assertions)]
  pub fn get_collab_backup_list(&self, uid: i64) -> Vec<String> {
    self.authenticate_user.database.get_collab_backup_list(uid)
  }

  /// Performs a user sign-in, initializing user awareness and sending relevant notifications.
  ///
  /// This asynchronous function interacts with an external user service to authenticate and sign in a user
  /// based on provided parameters. Once signed in, it updates the collaboration configuration, logs the user,
  /// saves their workspaces, and initializes their user awareness.
  ///
  /// A sign-in notification is also sent after a successful sign-in.
  ///
  #[tracing::instrument(level = "info", skip(self, params))]
  pub async fn sign_in(
    &self,
    params: SignInParams,
    authenticator: Authenticator,
  ) -> Result<UserProfile, FlowyError> {
    self.cloud_services.set_user_authenticator(&authenticator);

    let response: AuthResponse = self
      .cloud_services
      .get_user_service()?
      .sign_in(BoxAny::new(params))
      .await?;
    let session = Session::from(&response);
    self.prepare_user(&session).await;

    let latest_workspace = response.latest_workspace.clone();
    let user_profile = UserProfile::from((&response, &authenticator));
    self
      .save_auth_data(&response, &authenticator, &session)
      .await?;

    let _ = self
      .initial_user_awareness(&session, &user_profile.authenticator)
      .await;
    self
      .user_status_callback
      .read()
      .await
      .did_sign_in(
        user_profile.uid,
        &latest_workspace,
        &self.authenticate_user.user_config.device_id,
        &authenticator,
      )
      .await?;
    send_auth_state_notification(AuthStateChangedPB {
      state: AuthStatePB::AuthStateSignIn,
      message: "Sign in success".to_string(),
    });
    Ok(user_profile)
  }

  /// Manages the user sign-up process, potentially migrating data if necessary.
  ///
  /// This asynchronous function interacts with an external authentication service to register and sign up a user
  /// based on the provided parameters. Following a successful sign-up, it handles configuration updates, logging,
  /// and saving workspace information. If a user is signing up with a new profile and previously had guest data,
  /// this function may migrate that data over to the new account.
  ///
  #[tracing::instrument(level = "info", skip(self, params))]
  pub async fn sign_up(
    &self,
    authenticator: Authenticator,
    params: BoxAny,
  ) -> Result<UserProfile, FlowyError> {
    // sign out the current user if there is one
    let migration_user = self.get_migration_user(&authenticator).await;

    self.cloud_services.set_user_authenticator(&authenticator);
    let auth_service = self.cloud_services.get_user_service()?;
    let response: AuthResponse = auth_service.sign_up(params).await?;
    let new_user_profile = UserProfile::from((&response, &authenticator));
    if new_user_profile.encryption_type.require_encrypt_secret() {
      self.auth_process.lock().await.replace(UserAuthProcess {
        user_profile: new_user_profile.clone(),
        migration_user,
        response,
        authenticator,
      });
    } else {
      self
        .continue_sign_up(&new_user_profile, migration_user, response, &authenticator)
        .await?;
    }
    Ok(new_user_profile)
  }

  #[tracing::instrument(level = "info", skip(self))]
  pub async fn resume_sign_up(&self) -> Result<(), FlowyError> {
    let UserAuthProcess {
      user_profile,
      migration_user,
      response,
      authenticator,
    } = self
      .auth_process
      .lock()
      .await
      .clone()
      .ok_or(FlowyError::new(
        ErrorCode::Internal,
        "No resumable sign up data",
      ))?;
    self
      .continue_sign_up(&user_profile, migration_user, response, &authenticator)
      .await?;
    Ok(())
  }

  #[tracing::instrument(level = "info", skip_all, err)]
  async fn continue_sign_up(
    &self,
    new_user_profile: &UserProfile,
    migration_user: Option<AnonUser>,
    response: AuthResponse,
    authenticator: &Authenticator,
  ) -> FlowyResult<()> {
    let new_session = Session::from(&response);
    self.prepare_user(&new_session).await;
    self
      .save_auth_data(&response, authenticator, &new_session)
      .await?;
    let _ = self
      .initial_user_awareness(&new_session, &new_user_profile.authenticator)
      .await;
    self
      .user_status_callback
      .read()
      .await
      .did_sign_up(
        response.is_new_user,
        new_user_profile,
        &new_session.user_workspace,
        &self.authenticate_user.user_config.device_id,
        authenticator,
      )
      .await?;

    if response.is_new_user {
      // For new user, we don't need to run the migrations
      if let Ok(pool) = self
        .authenticate_user
        .database
        .get_pool(new_session.user_id)
      {
        mark_all_migrations_as_applied(&pool);
      } else {
        error!("Failed to get pool for user {}", new_session.user_id);
      }

      if let Some(old_user) = migration_user {
        event!(
          tracing::Level::INFO,
          "Migrate anon user data from {:?} to {:?}",
          old_user.session.user_id,
          new_user_profile.uid
        );
        self
          .migrate_anon_user_data_to_cloud(&old_user, &new_session, authenticator)
          .await?;
        self.remove_anon_user();
        let _ = self
          .authenticate_user
          .database
          .close(old_user.session.user_id);
      }
    }

    send_auth_state_notification(AuthStateChangedPB {
      state: AuthStatePB::AuthStateSignIn,
      message: "Sign up success".to_string(),
    });
    Ok(())
  }

  #[tracing::instrument(level = "info", skip(self))]
  pub async fn sign_out(&self) -> Result<(), FlowyError> {
    if let Ok(session) = self.get_session() {
      sign_out(
        &self.cloud_services,
        &session,
        &self.authenticate_user,
        self.db_connection(session.user_id)?,
      )
      .await?;
    }
    Ok(())
  }

  #[tracing::instrument(level = "info", skip(self))]
  pub async fn delete_account(&self) -> Result<(), FlowyError> {
    self
      .cloud_services
      .get_user_service()?
      .delete_account()
      .await?;
    Ok(())
  }

  /// Updates the user's profile with the given parameters.
  ///
  /// This function modifies the user's profile based on the provided update parameters. After updating, it
  /// sends a notification about the change. It's also responsible for handling interactions with the underlying
  /// database and updates user profile.
  ///
  #[tracing::instrument(level = "debug", skip(self))]
  pub async fn update_user_profile(
    &self,
    params: UpdateUserProfileParams,
  ) -> Result<(), FlowyError> {
    let changeset = UserTableChangeset::new(params.clone());
    let session = self.get_session()?;
    upsert_user_profile_change(
      session.user_id,
      self.db_connection(session.user_id)?,
      changeset,
    )?;

    let profile = self.get_user_profile_from_disk(session.user_id).await?;
    self
      .update_user(session.user_id, profile.token, params)
      .await?;
    Ok(())
  }

  pub async fn init_user(&self) -> Result<(), FlowyError> {
    Ok(())
  }

  pub async fn prepare_user(&self, session: &Session) {
    let _ = self.authenticate_user.database.close(session.user_id);
  }

  pub async fn prepare_backup(&self, session: &Session) {
    // Ensure to backup user data if a cloud drive is used for storage. While using a cloud drive
    // for storing user data is not advised due to potential data corruption risks, in scenarios where
    // users opt for cloud storage, the application should automatically create a backup of the user
    // data. This backup should be in the form of a zip file and stored locally on the user's disk
    // for safety and data integrity purposes
    self
      .authenticate_user
      .database
      .backup(session.user_id, &session.user_workspace.id);
  }

  /// Fetches the user profile for the given user ID.
  pub async fn get_user_profile_from_disk(&self, uid: i64) -> Result<UserProfile, FlowyError> {
    select_user_profile(uid, self.db_connection(uid)?)
  }

  #[tracing::instrument(level = "info", skip_all, err)]
  pub async fn refresh_user_profile(&self, old_user_profile: &UserProfile) -> FlowyResult<()> {
    // If the user is a local user, no need to refresh the user profile
    if old_user_profile.authenticator.is_local() {
      return Ok(());
    }

    let now = chrono::Utc::now().timestamp();
    // Add debounce to avoid too many requests
    if now - self.refresh_user_profile_since.load(Ordering::SeqCst) < 5 {
      return Ok(());
    }
    self.refresh_user_profile_since.store(now, Ordering::SeqCst);

    let uid = old_user_profile.uid;
    let result: Result<UserProfile, FlowyError> = self
      .cloud_services
      .get_user_service()?
      .get_user_profile(UserCredentials::from_uid(uid))
      .await;

    match result {
      Ok(new_user_profile) => {
        // If the user profile is updated, save the new user profile
        if new_user_profile.updated_at > old_user_profile.updated_at {
          validate_encryption_sign(old_user_profile, &new_user_profile.encryption_type.sign());
          // Save the new user profile
          let changeset = UserTableChangeset::from_user_profile(new_user_profile);
          let _ = upsert_user_profile_change(
            uid,
            self.authenticate_user.database.get_connection(uid)?,
            changeset,
          );
        }
        Ok(())
      },
      Err(err) => {
        if err.is_local_version_not_support() {
          return Ok(());
        }
        // If the user is not found, notify the frontend to logout
        if err.is_unauthorized() {
          event!(
            tracing::Level::ERROR,
            "User is unauthorized, sign out the user"
          );

          self.sign_out().await?;
          send_auth_state_notification(AuthStateChangedPB {
            state: AuthStatePB::InvalidAuth,
            message: "User is not found on the server".to_string(),
          });
        }
        Err(err)
      },
    }
  }

  #[instrument(level = "info", skip_all)]
  pub fn user_dir(&self, uid: i64) -> String {
    self.authenticate_user.user_paths.user_data_dir(uid)
  }

  pub fn user_setting(&self) -> Result<UserSettingPB, FlowyError> {
    let session = self.get_session()?;
    let user_setting = UserSettingPB {
      user_folder: self.user_dir(session.user_id),
    };
    Ok(user_setting)
  }

  pub fn user_id(&self) -> Result<i64, FlowyError> {
    Ok(self.get_session()?.user_id)
  }

  pub fn workspace_id(&self) -> Result<String, FlowyError> {
    let session = self.get_session()?;
    Ok(session.user_workspace.id.clone())
  }

  pub fn token(&self) -> Result<Option<String>, FlowyError> {
    Ok(None)
  }

  async fn update_user(
    &self,
    uid: i64,
    token: String,
    params: UpdateUserProfileParams,
  ) -> Result<(), FlowyError> {
    let server = self.cloud_services.get_user_service()?;
    tokio::spawn(async move {
      let credentials = UserCredentials::new(Some(token), Some(uid), None);
      server.update_user(credentials, params).await
    })
    .await
    .map_err(internal_error)??;
    Ok(())
  }

  async fn save_user(&self, uid: i64, user: UserTable) -> Result<(), FlowyError> {
    let mut conn = self.db_connection(uid)?;
    conn.immediate_transaction(|conn| {
      // delete old user if exists
      diesel::delete(user_table::dsl::user_table.filter(user_table::dsl::id.eq(&user.id)))
        .execute(conn)?;

      let _ = diesel::insert_into(user_table::table)
        .values(user)
        .execute(conn)?;
      Ok::<(), FlowyError>(())
    })?;

    Ok(())
  }

  pub async fn receive_realtime_event(&self, json: Value) {
    if let Ok(user_service) = self.cloud_services.get_user_service() {
      user_service.receive_realtime_event(json)
    }
  }

  pub(crate) async fn generate_sign_in_url_with_email(
    &self,
    authenticator: &Authenticator,
    email: &str,
  ) -> Result<String, FlowyError> {
    self.cloud_services.set_user_authenticator(authenticator);

    let auth_service = self.cloud_services.get_user_service()?;
    let url = auth_service.generate_sign_in_url_with_email(email).await?;
    Ok(url)
  }

  pub(crate) async fn sign_in_with_magic_link(
    &self,
    email: &str,
    redirect_to: &str,
  ) -> Result<(), FlowyError> {
    self
      .cloud_services
      .set_user_authenticator(&Authenticator::AppFlowyCloud);
    let auth_service = self.cloud_services.get_user_service()?;
    auth_service
      .sign_in_with_magic_link(email, redirect_to)
      .await?;
    Ok(())
  }

  pub(crate) async fn generate_oauth_url(
    &self,
    oauth_provider: &str,
  ) -> Result<String, FlowyError> {
    self
      .cloud_services
      .set_user_authenticator(&Authenticator::AppFlowyCloud);
    let auth_service = self.cloud_services.get_user_service()?;
    let url = auth_service
      .generate_oauth_url_with_provider(oauth_provider)
      .await?;
    Ok(url)
  }

  #[instrument(level = "info", skip_all, err)]
  async fn save_auth_data(
    &self,
    response: &impl UserAuthResponse,
    authenticator: &Authenticator,
    session: &Session,
  ) -> Result<(), FlowyError> {
    let user_profile = UserProfile::from((response, authenticator));
    let uid = user_profile.uid;
    if authenticator.is_local() {
      event!(tracing::Level::DEBUG, "Save new anon user: {:?}", uid);
      self.set_anon_user(session);
    }

    save_all_user_workspaces(uid, self.db_connection(uid)?, response.user_workspaces())?;
    info!(
      "Save new user profile to disk, authenticator: {:?}",
      authenticator
    );

    self
      .authenticate_user
      .set_session(Some(session.clone().into()))?;
    self
      .save_user(uid, (user_profile, authenticator.clone()).into())
      .await?;
    Ok(())
  }

  async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> {
    let session = self.get_session()?;
    if session.user_id == user_update.uid {
      debug!("Receive user update: {:?}", user_update);
      let user_profile = self.get_user_profile_from_disk(user_update.uid).await?;
      if !validate_encryption_sign(&user_profile, &user_update.encryption_sign) {
        return Ok(());
      }

      // Save the user profile change
      upsert_user_profile_change(
        user_update.uid,
        self.db_connection(user_update.uid)?,
        UserTableChangeset::from(user_update),
      )?;
    }

    Ok(())
  }

  async fn migrate_anon_user_data_to_cloud(
    &self,
    old_user: &AnonUser,
    _new_user_session: &Session,
    authenticator: &Authenticator,
  ) -> Result<(), FlowyError> {
    let old_collab_db = self
      .authenticate_user
      .database
      .get_collab_db(old_user.session.user_id)?;

    if authenticator == &Authenticator::AppFlowyCloud {
      self
        .migration_anon_user_on_appflowy_cloud_sign_up(old_user, &old_collab_db)
        .await?;
    }

    // Save the old user workspace setting.
    save_user_workspace(
      old_user.session.user_id,
      self
        .authenticate_user
        .database
        .get_connection(old_user.session.user_id)?,
      &old_user.session.user_workspace.clone(),
    )?;
    Ok(())
  }
}

fn current_authenticator() -> Authenticator {
  match AuthenticatorType::from_env() {
    AuthenticatorType::Local => Authenticator::Local,
    AuthenticatorType::AppFlowyCloud => Authenticator::AppFlowyCloud,
  }
}

pub fn upsert_user_profile_change(
  uid: i64,
  mut conn: DBConnection,
  changeset: UserTableChangeset,
) -> FlowyResult<()> {
  event!(
    tracing::Level::DEBUG,
    "Update user profile with changeset: {:?}",
    changeset
  );
  diesel_update_table!(user_table, changeset, &mut *conn);
  let user: UserProfile = user_table::dsl::user_table
    .filter(user_table::id.eq(&uid.to_string()))
    .first::<UserTable>(&mut *conn)?
    .into();
  send_notification(&uid.to_string(), UserNotification::DidUpdateUserProfile)
    .payload(UserProfilePB::from(user))
    .send();
  Ok(())
}

#[instrument(level = "info", skip_all, err)]
fn save_user_token(uid: i64, conn: DBConnection, token: String) -> FlowyResult<()> {
  let params = UpdateUserProfileParams::new(uid).with_token(token);
  let changeset = UserTableChangeset::new(params);
  upsert_user_profile_change(uid, conn, changeset)
}

#[instrument(level = "info", skip_all, err)]
fn remove_user_token(uid: i64, mut conn: DBConnection) -> FlowyResult<()> {
  diesel::update(user_table::dsl::user_table.filter(user_table::id.eq(&uid.to_string())))
    .set(user_table::token.eq(""))
    .execute(&mut *conn)?;
  Ok(())
}

fn collab_migration_list() -> Vec<Box<dyn UserDataMigration>> {
  // ⚠️The order of migrations is crucial. If you're adding a new migration, please ensure
  // it's appended to the end of the list.
  vec![
    Box::new(HistoricalEmptyDocumentMigration),
    Box::new(FavoriteV1AndWorkspaceArrayMigration),
    Box::new(WorkspaceTrashMapToSectionMigration),
    Box::new(CollabDocKeyWithWorkspaceIdMigration),
  ]
}

fn mark_all_migrations_as_applied(sqlite_pool: &Arc<ConnectionPool>) {
  if let Ok(mut conn) = sqlite_pool.get() {
    for migration in collab_migration_list() {
      save_migration_record(&mut conn, migration.name());
    }
    info!("Mark all migrations as applied");
  }
}

pub(crate) fn run_collab_data_migration(
  session: &Session,
  user: &UserProfile,
  collab_db: Arc<CollabKVDB>,
  sqlite_pool: Arc<ConnectionPool>,
  kv: Arc<KVStorePreferences>,
  app_version: &Version,
) {
  let migrations = collab_migration_list();
  match UserLocalDataMigration::new(session.clone(), collab_db, sqlite_pool, kv).run(
    migrations,
    &user.authenticator,
    app_version,
  ) {
    Ok(applied_migrations) => {
      if !applied_migrations.is_empty() {
        info!(
          "[Migration]: did apply migrations: {:?}",
          applied_migrations
        );
      }
    },
    Err(e) => error!("[AppflowyData]:User data migration failed: {:?}", e),
  }
}

pub async fn sign_out(
  cloud_services: &Arc<dyn UserCloudServiceProvider>,
  session: &Session,
  authenticate_user: &AuthenticateUser,
  conn: DBConnection,
) -> Result<(), FlowyError> {
  let _ = remove_user_token(session.user_id, conn);
  authenticate_user.database.close(session.user_id)?;
  authenticate_user.set_session(None)?;

  let server = cloud_services.get_user_service()?;
  if let Err(err) = server.sign_out(None).await {
    event!(tracing::Level::ERROR, "{:?}", err);
  }

  Ok(())
}
