# frozen_string_literal: true

module BackupRestore
  RestoreDisabledError = Class.new(RuntimeError)
  FilenameMissingError = Class.new(RuntimeError)

  class Restorer
    delegate :log, to: :@logger, private: true

    attr_reader :success

    def initialize(user_id:, filename:, factory:, disable_emails: true)
      @user_id = user_id
      @filename = filename
      @factory = factory
      @logger = factory.logger
      @disable_emails = disable_emails

      ensure_restore_is_enabled
      ensure_we_have_a_user
      ensure_we_have_a_filename

      @success = false
      @current_db = RailsMultisite::ConnectionManagement.current_db

      @system = factory.create_system_interface
      @backup_file_handler = factory.create_backup_file_handler(@filename, @current_db)
      @database_restorer = factory.create_database_restorer(@current_db)
      @uploads_restorer = factory.create_uploads_restorer
    end

    def run
      log "[STARTED]"
      log "'#{@user_info[:username]}' has started the restore!"

      # FIXME not atomic!
      ensure_no_operation_is_running
      @system.mark_restore_as_running

      @system.listen_for_shutdown_signal

      @tmp_directory, db_dump_path = @backup_file_handler.decompress
      validate_backup_metadata

      @system.enable_readonly_mode
      @system.pause_sidekiq
      @system.wait_for_sidekiq

      @database_restorer.restore(db_dump_path)

      reload_site_settings

      @system.disable_readonly_mode

      clear_category_cache
      clear_emoji_cache
      clear_theme_cache
      reload_translations

      @uploads_restorer.restore(@tmp_directory)

      after_restore_hook
    rescue Compression::Strategy::ExtractFailed
      log 'ERROR: The uncompressed file is too big. Consider increasing the hidden ' \
          '"decompressed_backup_max_file_size_mb" setting.'
      @database_restorer.rollback
    rescue SystemExit
      log "Restore process was cancelled!"
      @database_restorer.rollback
    rescue => ex
      log "EXCEPTION: " + ex.message
      log ex.backtrace.join("\n")
      @database_restorer.rollback
    else
      @success = true
    ensure
      clean_up
      notify_user
      log "Finished!"

      @success ? log("[SUCCESS]") : log("[FAILED]")
    end

    protected

    def ensure_restore_is_enabled
      return if Rails.env.development? || SiteSetting.allow_restore?
      raise BackupRestore::RestoreDisabledError
    end

    def ensure_no_operation_is_running
      raise BackupRestore::OperationRunningError if BackupRestore.is_operation_running?
    end

    def ensure_we_have_a_user
      user = User.find_by(id: @user_id)
      raise Discourse::InvalidParameters.new(:user_id) if user.blank?

      # keep some user data around to check them against the newly restored database
      @user_info = { id: user.id, username: user.username, email: user.email }
    end

    def ensure_we_have_a_filename
      raise BackupRestore::FilenameMissingError if @filename.nil?
    end

    def validate_backup_metadata
      @factory.create_meta_data_handler(@filename, @tmp_directory).validate
    end

    def reload_site_settings
      log "Reloading site settings..."
      SiteSetting.refresh!

      DiscourseEvent.trigger(:site_settings_restored)

      if @disable_emails && SiteSetting.disable_emails == 'no'
        log "Disabling outgoing emails for non-staff users..."
        user = User.find_by_email(@user_info[:email]) || Discourse.system_user
        SiteSetting.set_and_log(:disable_emails, 'non-staff', user)
      end
    end

    def clear_category_cache
      log "Clearing category cache..."
      Category.reset_topic_ids_cache
      Category.clear_subcategory_ids
    end

    def clear_emoji_cache
      log "Clearing emoji cache..."
      Emoji.clear_cache
    end

    def reload_translations
      log "Reloading translations..."
      TranslationOverride.reload_all_overrides!
    end

    def notify_user
      if user = User.find_by_email(@user_info[:email])
        log "Notifying '#{user.username}' of the end of the restore..."
        status = @success ? :restore_succeeded : :restore_failed

        SystemMessage.create_from_system_user(
          user, status,
          logs: Discourse::Utils.pretty_logs(@logger.logs)
        )
      else
        log "Could not send notification to '#{@user_info[:username]}' " \
          "(#{@user_info[:email]}), because the user does not exist."
      end
    rescue => ex
      log "Something went wrong while notifying user.", ex
    end

    def clean_up
      log "Cleaning stuff up..."
      @database_restorer.clean_up
      @backup_file_handler.clean_up
      @system.unpause_sidekiq
      @system.disable_readonly_mode if Discourse.readonly_mode?
      @system.mark_restore_as_not_running
    end

    def clear_theme_cache
      log "Clear theme cache"
      ThemeField.force_recompilation!
      Theme.expire_site_cache!
      Stylesheet::Manager.cache.clear
    end

    def after_restore_hook
      log "Executing the after_restore_hook..."
      DiscourseEvent.trigger(:restore_complete)
    end
  end
end
