namespace :data_fix do

DATAFIX_DIR = File.join("db", "migrate-data")

def puts_and_log message, level = :info
  puts "** #{message}"
  logger = Rails.logger
  logger.send(level) { |msg| "data_fix: #{message}" }
end

# strip characters and whitespace to create valid filenames, also lowercase
def sanitize_filename(name)
  if(name.is_a? Integer)
    return name.to_s
  end
  return name.tr(
           "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÑñÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
           "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
         ).downcase.strip.gsub(' ', '-').gsub(/[^\w.-]/, '')
end

def exit_if_not_installed
  if not ActiveRecord::Base.connection.table_exists? :data_fixes
    puts_and_log "ERROR: it seems like data_fix:init has not been run yet", :error
    exit(1)
  end
end

desc 'Add a :data_fixes table to the DB (run the first time only)'
task :init => :environment do
  unless ActiveRecord::Base.connection.table_exists? :data_fixes
    ActiveRecord::Base.connection.create_table :data_fixes do |t|
      t.string :name
      t.timestamps
    end
  end
  Dir.mkdir DATAFIX_DIR if not Dir.exists? DATAFIX_DIR
end

desc 'Create a new data fix'
task :create, [:name] => :environment do |_, args|
  exit_if_not_installed

  time = DateTime.now.strftime("%Y%m%d%H%M%S")
  f = File.new(File.join(DATAFIX_DIR, sanitize_filename("#{time}_#{args.name}.rb")), "w")
  f.write("")
  f.close
end

def backup_database dry_run
  config = Rails.configuration.database_configuration[Rails.env]

  puts_and_log "Backing up #{Rails.env} database"
  case config["adapter"]
  when "sqlite3"
    db_name = config["database"]
    backup_db_name = db_name.gsub(/\.sqlite3$/, "-#{Date.today.strftime('%Y-%m-%d')}.sqlite3")
    cmd = "cp \"#{db_name}\" \"#{backup_db_name}\""
  when "mysql2"
    db_host = config["host"]
    db_name = config["database"]
    db_username = config["username"]
    db_password = config["password"]
    backup_db_name = "db/#{db_name}-#{Rails.env}-#{Date.today.strftime('%Y-%m-%d')}.sql"
    cmd = "mysqldump -u #{db_name} -p #{db_password} #{db_name} > \"#{backup_db_name}\""
  end
  if dry_run
    puts_and_log "WOULD RUN: #{cmd}"
  else
    system cmd
  end
  puts_and_log "backup performed and available as #{backup_db_name}."
end

def df_status
  files = Dir.glob("*.rb", base: DATAFIX_DIR).sort

  puts "** data_fix status"
  printf "%3s %-60s %-10s\n", "run", "file in #{DATAFIX_DIR}", "when"
  files.each do |file|
    run = DataFix::DataFix.find_by(name: file)
    printf "%-3s %-60s %-10s\n", run ? "Y" : "N", file, run ? run.created_at : ""
  end
  puts ""
end

def df_run dry_run = true, steps = nil, no_backup = false
  df_status

  files = Dir.glob("*.rb", base: DATAFIX_DIR).sort
  already_run = DataFix::DataFix.all.map { |x| x.name }.sort

  to_run = steps ? (files - already_run)[0..(steps.to_i - 1)] : (files - already_run)
  puts_and_log "data fix to run: #{to_run.join(", ")}"

  # if we are doing things for real, backup the DB, unless
  # explicitly asked not to do it
  backup_database(dry_run) unless no_backup

  # sort filenames by date (same as sorting alphabetically, since
  # the date is in the name
  to_run.sort.each do |file|
    puts_and_log "starting data migration '#{file}'"
    if dry_run
      puts_and_log "WOULD RUN: ${File.join(DATAFIX_DIR, file)}" 
    else
      load File.join(DATAFIX_DIR, file)
      DataFix::DataFix.create(name: file) 
    end
    puts_and_log "done!"
  end
end

desc 'Dry run the required data fixes (STEP= to specify the optional number of steps)'
task dry_run: :environment do
  exit_if_not_installed
  df_run true, ENV["STEP"]
end

desc 'Run the required data fixes (STEP= to specify the optional number of steps)'
task run: :environment do
  exit_if_not_installed
  df_run false, ENV["STEP"]
end

desc 'Status: show which data fixes have been run'
task status: :environment do
  exit_if_not_installed
  df_status
end

desc 'Reconcile: declare that all data fixes have been run'
task reconcile: :environment do
  exit_if_not_installed

  DataFix::DataFix.destroy_all

  files = Dir.glob("*.rb", base: DATAFIX_DIR).sort
  files.each do |file|
    DataFix::DataFix.create(name: file) 
  end
  puts_and_log "all data fixes have now been *declared* as run"
end

desc 'Reconcile up to: declare that data fixes up to given filename (included) have been run'
task :reconcile_up_to, [:filename] => :environment do |task, args|
  exit_if_not_installed

  stop_file = args[:filename]

  DataFix::DataFix.destroy_all
  files = Dir.glob("*.rb", base: DATAFIX_DIR).sort
  files.each do |file|
    DataFix::DataFix.create(name: file) 
    exit if file == stop_file
  end
end

desc 'Rollback: declare that the latest STEP data fixes have not been run (default 1)'
task rollback: :environment do
  exit_if_not_installed

  step = ENV["STEP"] || 1
  step.times do 
    DataFix::DataFix.last.destroy
  end
end

end