namespace :db do
  namespace :statistics do
    desc "Import statistics from MS to app"
    task :import, [:config_name] => :environment do |_, args|
      StatisticsTask.new(args[:config_name]).process!
    end
  end
end

class StatisticsTask
  def initialize(config_name)
    config_name ||= 'default'
    config_path = Rails.root.join('config', 'statistics', "#{config_name.sub(/\.yml$/, '')}.yml").to_s
    @config = YAML.load(Pathname(config_path).read).deep_symbolize_keys
  end

  def process!
    create_count = 0
    warning_count = 0
    time_different = @config[:time_different].try(:to_i) || 0

    ms_statistics = load_ms_statistic
    user_ids = User.where(login: ms_statistics.pluck(:endUserName)).pluck(:login, :id).to_h
    image_ids = Image.where(ms_image_id: ms_statistics.pluck(:pictureID)).pluck(:ms_image_id, :id).to_h
    operation_label_id = OperationLabel.where(label: 'HD').first.id

    ms_statistics.each do |ms_statistic|
      user_id = user_ids[ms_statistic.endUserName]
      image_id = image_ids[ms_statistic.pictureID]
      if user_id && image_id
        statistics = Statistic.where(created_at: [in_paris_time(ms_statistic.operTime) - time_different.minute..in_paris_time(ms_statistic.operTime) + time_different.minute])
        stat = statistics.find_or_initialize_by user_id: user_id,
                                                image_id: image_id,
                                                operation_label_id: operation_label_id
        if stat.new_record?
          stat.assign_attributes created_at: in_paris_time(ms_statistic.operTime),
                                 updated_at: in_paris_time(ms_statistic.operTime)
          if stat.save
            create_count += 1
          else
            logger.warn("stat cannot be saved for endUserName = #{ms_statistic.endUserName}, pictureID = #{ms_statistic.pictureID}: #{stat.errors.full_messages.join(', ')}")
            warning_count += 1
          end
        end
      else
        case
        when !user_id && !image_id
          logger.warn("no user_id and image_id for endUserName = #{ms_statistic.endUserName}, pictureID = #{ms_statistic.pictureID}")
        when !user_id
          logger.warn("no user_id for endUserName = #{ms_statistic.endUserName}")
        else
          logger.warn("no image_id for pictureID = #{ms_statistic.pictureID}")
        end
        warning_count += 1
      end
    end

    if ms_statistics.size > 0
      logger.info("Task done with #{ms_statistics.size} total record(s) found, #{create_count} new record(s) created, #{warning_count} warning(s)")
    end
  end

  private

  def load_ms_statistic
    age = @config[:age].try(:to_i)
    filters = prepare_filters(@config[:filters] || [])
    filter_query = prepare_query(filters)

    ms_statistics = Ms::Statistic.where(messageType: :downloaded)
    if age
      ms_statistics = ms_statistics.where('operTime >= ?', Time.now - age.minute)
    end
    if filter_query
      ms_statistics = ms_statistics.where(filter_query[:string].join(' OR '), *filter_query[:values])
    end
    ms_statistics
  end

  def prepare_filters(filters)
    filters.reduce({ login_title: [], login: [], title: [] }) do |scope, filter|
      scope.tap do |scope|
        case
        when filter[:login].present? && filter[:title].present?
          scope[:login_title].push([filter[:login], filter[:title]].join('_'))
        when filter[:login].present?
          scope[:login].push(filter[:login])
        when filter[:title].present?
          scope[:title].push(filter[:title])
        else
        end
      end
    end
  end

  def prepare_query(filters)
    query = { string: [], values: [] }
    if filters[:login_title].present?
      query[:string].push("CONCAT(endUserName, '_', titlesName) IN (?)")
      query[:values].push(filters[:login_title])
    end
    if filters[:login].present?
      query[:string].push("endUserName IN (?)")
      query[:values].push(filters[:login])
    end
    if filters[:title].present?
      query[:string].push("titlesName IN (?)")
      query[:values].push(filters[:title])
    end
    if query[:string].blank?
      nil
    else
      query
    end
  end

  # force change to Paris time zone without changing the time
  # example: change from 2020-01-01 14:20:00 UTC to 2020-01-01 14:20:00 CET
  def in_paris_time(datetime)
    str = datetime.strftime('%Y-%m-%d %H:%M:%S')
    Time.use_zone('Paris') { str.in_time_zone }
  end

  def logger
    @logger ||= Logger.new(STDOUT)
  end
end
