#!/usr/bin/env ruby
require "json"
require "rainbow"
require "optparse"
require "set"
require_relative "../lib/deprecation_tracker/shard_merger"

def run_tests(deprecation_warnings, opts = {})
  tracker_mode = opts[:tracker_mode]
  next_mode = opts[:next_mode]
  rspec_command = if next_mode
    "bin/next rspec"
  else
    "bundle exec rspec"
  end

  command = "DEPRECATION_TRACKER=#{tracker_mode} #{rspec_command} #{deprecation_warnings.keys.join(" ")}"
  puts command
  exec command
end

def print_info(deprecation_warnings, opts = {})
  verbose = !!opts[:verbose]
  frequency_by_message = deprecation_warnings.each_with_object({}) do |(test_file, messages), hash|
    messages.each do |message|
      hash[message] ||= { test_files: Set.new, occurrences: 0 }
      hash[message][:test_files] << test_file
      hash[message][:occurrences] += 1
    end
  end.sort_by {|message, data| data[:occurrences] }.reverse.to_h

  puts Rainbow("Ten most common deprecation warnings:").underline
  frequency_by_message.take(10).each do |message, data|
    puts Rainbow("Occurrences: #{data.fetch(:occurrences)}").bold
    puts "Test files: #{data.fetch(:test_files).to_a.join(" ")}" if verbose
    puts Rainbow(message).red
    puts "----------"
  end
end

options = {}
option_parser = OptionParser.new do |opts|
  opts.banner = <<-MESSAGE
    Usage: #{__FILE__.to_s} [options] [mode]

      Parses the deprecation warning shitlist and show info or run tests.

    Examples:
      bin/deprecations info # Show top ten deprecations
      bin/deprecations --next info # Show top ten deprecations for Rails 5
      bin/deprecations --pattern "ActiveRecord::Base" --verbose info # Show full details on deprecations matching pattern
      bin/deprecations --tracker-mode save --pattern "pass" run # Run tests that output deprecations matching pattern and update shitlist
      bin/deprecations merge --delete-shards # Merge parallel CI shards and remove shard files

    Modes:
      info
        Show information on the ten most frequent deprceation warnings.

      run
        Run tests that are known to cause deprecation warnings. Use --pattern to filter what tests are run.

      merge
        Merge parallel CI shard files into the canonical shitlist. Use with --delete-shards to remove shard files after merging.

    Options:
  MESSAGE

  opts.on("--next", "Run against the next shitlist") do |next_mode|
    options[:next] = next_mode
  end

  opts.on("--tracker-mode MODE", "Set DEPRECATION_TRACKER in test mode. Options: save or compare") do |tracker_mode|
    options[:tracker_mode] = tracker_mode
  end

  opts.on("--pattern RUBY_REGEX", "Filter deprecation warnings with a pattern.") do |pattern|
    options[:pattern] = pattern
  end

  opts.on("--verbose", "show more information") do
    options[:verbose] = true
  end

  opts.on("--delete-shards", "Delete shard files after merging") do
    options[:delete_shards] = true
  end

  opts.on_tail("-h", "--help", "Prints this help") do
    puts opts
    exit
  end
end

option_parser.parse!

options[:mode] = ARGV.last
path = options[:next] ? "spec/support/deprecation_warning.next.shitlist.json" : "spec/support/deprecation_warning.shitlist.json"

case options[:mode]
when "merge"
  output = DeprecationTracker::ShardMerger.new(path, delete_shards: !!options[:delete_shards]).merge
  shards = output[:shards]
  result = output[:result]
  total_messages = result.values.map(&:size).reduce(0, :+)
  puts "Merged #{shards} shard files into #{path} (#{result.size} buckets, #{total_messages} deprecation messages)"
when "run", "info"
  pattern_string = options.fetch(:pattern, ".+")
  pattern = /#{pattern_string}/

  deprecation_warnings = JSON.parse(File.read(path)).each_with_object({}) do |(test_file, messages), hash|
    filtered_messages = messages.select {|message| message.match(pattern) }
    hash[test_file] = filtered_messages if !filtered_messages.empty?
  end

  if deprecation_warnings.empty?
    abort "No test files with deprecations matching #{pattern.inspect}."
    exit 2
  end

  if options[:mode] == "run"
    run_tests(deprecation_warnings, next_mode: options[:next], tracker_mode: options[:tracker_mode])
  else
    print_info(deprecation_warnings, verbose: options[:verbose])
  end
when nil
  STDERR.puts Rainbow("Must pass a mode: run, info, or merge").red
  puts option_parser
  exit 1
else
  STDERR.puts Rainbow("Unknown mode: #{options[:mode]}").red
  exit 1
end
