require_dependency 'export/json_encoder'
require_dependency 'export/export'
require_dependency 'import/import'

module Jobs

  class Exporter < Jobs::Base

    sidekiq_options retry: false

    def execute(args)
      raise Import::ImportInProgressError if Import::is_import_running?
      raise Export::ExportInProgressError if Export::is_export_running?

      @format = args[:format] || :json

      @output_base_filename = File.absolute_path( args[:filename] || File.join( Rails.root, 'tmp', "export-#{Time.now.strftime('%Y-%m-%d-%H%M%S')}" ) )
      @output_base_filename = @output_base_filename[0...-3] if @output_base_filename[-3..-1] == '.gz'
      @output_base_filename = @output_base_filename[0...-4] if @output_base_filename[-4..-1] == '.tar'

      @user = args[:user_id] ? User.where(id: args[:user_id].to_i).first : nil

      start_export
      @encoder.write_schema_info( source: 'discourse', version: Export.current_schema_version )
      ordered_models_for_export.each do |model|
        log "  #{model.table_name}"
        column_info = model.columns
        order_col = column_info.map(&:name).find {|x| x == 'id'} || order_columns_for(model)
        @encoder.write_table(model.table_name, column_info) do |num_rows_written|
          if order_col
            model.connection.select_rows("select * from #{model.table_name} order by #{order_col} limit #{batch_size} offset #{num_rows_written}")
          else
            # Take the rows in the order the database returns them
            log "WARNING: no order by clause is being used for #{model.name} (#{model.table_name}). Please update Jobs::Exporter order_columns_for for #{model.name}."
            model.connection.select_rows("select * from #{model.table_name} limit #{batch_size} offset #{num_rows_written}")
          end
        end
      end
      "#{@output_base_filename}.tar.gz"
    ensure
      finish_export
    end

    def ordered_models_for_export
      Export.models_included_in_export
    end

    def order_columns_for(model)
      @order_columns_for_hash ||= {
        'CategoryFeaturedTopic' => 'category_id, topic_id',
        'PostOneboxRender'      => 'post_id, onebox_render_id',
        'PostReply'             => 'post_id, reply_id',
        'PostTiming'            => 'topic_id, post_number, user_id',
        'TopicUser'             => 'topic_id, user_id',
        'View'                  => 'parent_id, parent_type, ip, viewed_at'
      }
      @order_columns_for_hash[model.name]
    end

    def batch_size
      1000
    end

    def start_export
      if @format == :json
        @encoder = Export::JsonEncoder.new
      else
        raise Export::FormatInvalidError
      end
      Export.set_export_started
      Discourse.enable_maintenance_mode
    end

    def finish_export
      if @encoder
        @encoder.finish
        create_tar_file
        @encoder.cleanup_temp
      end
    ensure
      Export.set_export_is_not_running
      Discourse.disable_maintenance_mode
      send_notification
    end

    def create_tar_file
      filenames = @encoder.filenames

      FileUtils.cd( File.dirname(filenames.first) ) do
        `tar cvf #{@output_base_filename}.tar #{File.basename(filenames.first)}`
      end

      FileUtils.cd( File.join(Rails.root, 'public') ) do
        Upload.find_each do |upload|
          `tar rvf #{@output_base_filename}.tar #{upload.url[1..-1]}` unless upload.url[0,4] == 'http'
        end
      end

      `gzip #{@output_base_filename}.tar`

      true
    end

    def send_notification
      SystemMessage.new(@user).create('export_succeeded') if @user
      true
    end

    def log(*args)
      puts args
      args.each do |arg|
        Rails.logger.info "#{Time.now.to_formatted_s(:db)}: [EXPORTER] #{arg}"
      end
      true
    end

  end

end
