require "benchmark"

STATUS_COUNTS = [10, 20, 40, 80].freeze
TRACKER_IDS = [1].freeze
ROLE_IDS = [1].freeze
ITERATIONS = 5

def build_status_ids(count)
  (100_000..(100_000 + count - 1)).to_a
end

def build_transitions(status_ids)
  status_ids.each_with_object({}) do |old_status_id, transitions|
    transitions[old_status_id.to_s] =
      status_ids.each_with_object({}) do |new_status_id, by_new_status|
        by_new_status[new_status_id.to_s] = {
          "always" => "1",
          "author" => "1",
          "assignee" => "1"
        }
      end
  end
end

def seed_records!(status_ids, tracker_ids, role_ids)
  rows = []

  tracker_ids.each do |tracker_id|
    role_ids.each do |role_id|
      status_ids.each do |old_status_id|
        status_ids.each do |new_status_id|
          rows << {
            :tracker_id => tracker_id,
            :role_id => role_id,
            :old_status_id => old_status_id,
            :new_status_id => new_status_id,
            :author => false,
            :assignee => false
          }
          rows << {
            :tracker_id => tracker_id,
            :role_id => role_id,
            :old_status_id => old_status_id,
            :new_status_id => new_status_id,
            :author => false,
            :assignee => true
          }
        end
      end
    end
  end

  WorkflowTransition.insert_all!(rows)
end

def delete_seed_records!(status_ids, tracker_ids, role_ids)
  WorkflowTransition.where(
    :tracker_id => tracker_ids,
    :role_id => role_ids,
    :old_status_id => status_ids,
    :new_status_id => status_ids
  ).delete_all
end

def original_replace_transitions(trackers, roles, transitions)
  trackers = Array.wrap(trackers)
  roles = Array.wrap(roles)

  WorkflowTransition.transaction do
    records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).to_a

    transitions.each do |old_status_id, transitions_by_new_status|
      transitions_by_new_status.each do |new_status_id, transition_by_rule|
        transition_by_rule.each do |rule, transition|
          trackers.each do |tracker|
            roles.each do |role|
              w = records.select do |r|
                r.old_status_id == old_status_id.to_i &&
                  r.new_status_id == new_status_id.to_i &&
                  r.tracker_id == tracker.id &&
                  r.role_id == role.id &&
                  !r.destroyed?
              end
              if rule == "always"
                w = w.select {|r| !r.author && !r.assignee}
              else
                w = w.select {|r| r.author || r.assignee}
              end
              if w.size > 1
                w[1..-1].each(&:destroy)
              end
              w = w.first

              if ["1", true].include?(transition)
                unless w
                  w = WorkflowTransition.new(
                    :old_status_id => old_status_id,
                    :new_status_id => new_status_id,
                    :tracker_id => tracker.id,
                    :role_id => role.id
                  )
                  records << w
                end
                w.author = true if rule == "author"
                w.assignee = true if rule == "assignee"
                w.save if w.changed?
              elsif w
                if rule == "always"
                  w.destroy
                elsif rule == "author"
                  if w.assignee
                    w.author = false
                    w.save if w.changed?
                  else
                    w.destroy
                  end
                elsif rule == "assignee"
                  if w.author
                    w.assignee = false
                    w.save if w.changed?
                  else
                    w.destroy
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end

def benchmark_variant(status_ids)
  tracker = Tracker.find(TRACKER_IDS.first)
  role = Role.find(ROLE_IDS.first)
  transitions = build_transitions(status_ids)

  before_durations = []
  after_durations = []

  ITERATIONS.times do
    delete_seed_records!(status_ids, TRACKER_IDS, ROLE_IDS)
    seed_records!(status_ids, TRACKER_IDS, ROLE_IDS)
    before_durations << Benchmark.realtime { original_replace_transitions(tracker, role, transitions) }

    delete_seed_records!(status_ids, TRACKER_IDS, ROLE_IDS)
    seed_records!(status_ids, TRACKER_IDS, ROLE_IDS)
    after_durations << Benchmark.realtime { WorkflowTransition.replace_transitions(tracker, role, transitions) }
  ensure
    delete_seed_records!(status_ids, TRACKER_IDS, ROLE_IDS)
  end

  {
    :count => status_ids.size,
    :before_avg => before_durations.sum / before_durations.size,
    :after_avg => after_durations.sum / after_durations.size
  }
end

results = STATUS_COUNTS.map do |count|
  benchmark_variant(build_status_ids(count))
end

puts "| statuses | before_avg_s | after_avg_s | speedup | reduction |"
puts "| ---: | ---: | ---: | ---: | ---: |"
results.each do |result|
  speedup = result[:before_avg] / result[:after_avg]
  reduction = (1 - (result[:after_avg] / result[:before_avg])) * 100
  puts "| #{result[:count]} | #{format('%.4f', result[:before_avg])} | #{format('%.4f', result[:after_avg])} | #{format('%.2f', speedup)}x | #{format('%.1f', reduction)}% |"
end

