# frozen_string_literal: true

module BackupRestore
  class BackupFileHandler
    OLD_DUMP_FILENAME = "dump.sql"

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

    def initialize(logger, filename, current_db, root_tmp_directory: Rails.root, location: nil)
      @logger = logger
      @filename = filename
      @current_db = current_db
      @root_tmp_directory = root_tmp_directory
      @is_archive = !(@filename =~ /\.sql\.gz\z/)
      @store_location = location
    end

    def decompress
      create_tmp_directory
      @archive_path = File.join(@tmp_directory, @filename)

      copy_archive_to_tmp_directory
      decompress_archive
      extract_db_dump

      [@tmp_directory, @db_dump_path]
    end

    def clean_up
      return if @tmp_directory.blank?

      log "Removing tmp '#{@tmp_directory}' directory..."
      FileUtils.rm_rf(@tmp_directory) if Dir[@tmp_directory].present?
    rescue => ex
      log "Something went wrong while removing the following tmp directory: #{@tmp_directory}", ex
    end

    protected

    def create_tmp_directory
      timestamp = Time.zone.now.strftime("%Y-%m-%d-%H%M%S")
      @tmp_directory = File.join(@root_tmp_directory, "tmp", "restores", @current_db, timestamp)
      ensure_directory_exists(@tmp_directory)
    end

    def ensure_directory_exists(directory)
      log "Making sure #{directory} exists..."
      FileUtils.mkdir_p(directory)
    end

    def copy_archive_to_tmp_directory
      store = BackupRestore::BackupStore.create(location: @store_location)

      if store.remote?
        log "Downloading archive to tmp directory..."
        failure_message = "Failed to download archive to tmp directory."
      else
        log "Copying archive to tmp directory..."
        failure_message = "Failed to copy archive to tmp directory."
      end

      store.download_file(@filename, @archive_path, failure_message)
    end

    def decompress_archive
      return if !@is_archive

      # the transformation is a workaround for a bug which existed between v2.6.0.beta1 and v2.6.0.beta2
      path_transformation =
        case tar_implementation
        when :gnu
          %w[--transform s|var/www/discourse/public/uploads/|uploads/|]
        when :bsd
          %w[-s |var/www/discourse/public/uploads/|uploads/|]
        end

      log "Unzipping archive, this may take a while..."
      Discourse::Utils.execute_command(
        "tar",
        "--extract",
        "--gzip",
        "--file",
        @archive_path,
        "--directory",
        @tmp_directory,
        *path_transformation,
        failure_message: "Failed to decompress archive.",
      )
    end

    def extract_db_dump
      @db_dump_path =
        if @is_archive
          # for compatibility with backups from Discourse v1.5 and below
          old_dump_path = File.join(@tmp_directory, OLD_DUMP_FILENAME)
          if File.exist?(old_dump_path)
            old_dump_path
          else
            File.join(@tmp_directory, BackupRestore::DUMP_FILE)
          end
        else
          File.join(@tmp_directory, @filename)
        end

      if File.extname(@db_dump_path) == ".gz"
        log "Extracting dump file..."
        Compression::Gzip.new.decompress(@tmp_directory, @db_dump_path, available_size)
        @db_dump_path.delete_suffix!(".gz")
      end

      @db_dump_path
    end

    def available_size
      SiteSetting.decompressed_backup_max_file_size_mb
    end

    def tar_implementation
      @tar_version ||=
        begin
          tar_version = Discourse::Utils.execute_command("tar", "--version")

          if tar_version.include?("GNU tar")
            :gnu
          elsif tar_version.include?("bsdtar")
            :bsd
          else
            raise "Unknown tar implementation: #{tar_version}"
          end
        end
    end
  end
end