begin
  require 'rubygems'
  require 'bundler'
  require 'rake'
  require 'json'
  require 'jsonlint/rake_task'
rescue LoadError
  puts "\e[31mCouldn't load required gems, have you run \e[33m`bundle exec tony check`\e[31m recently?\e[0m"
  puts "\e[31mYou prolly need to \e[33m`bundle update`\e[31m with the latest Kitchen...\e[0m"
  exit 2
end



def master?
  `git rev-parse --abbrev-ref HEAD`.strip == 'master'
end


def test_kitchen?
  File.exist? '.kitchen.yml'
end


def realm?
  !kitchen? && `git ls-files` =~ /\bBerksfile\.lock\b/
end


def kitchen?
  `git ls-files` =~ /\bGemfile\b/
end


def versioned?
  File.exist? 'VERSION'
end


def current_version
  File.read('VERSION').strip
end


def youre_dirty?
  dirty = `git diff HEAD --numstat`.split("\n").length > 0
  raise '`git diff` failed' unless $?.exitstatus.zero?
  dirty
end


def youre_dirty!
  if youre_dirty?
    raise "You you have uncommitted changes! Commit or stash before continuing."
  end
end


def youre_behind?
  `git fetch --tags`
  raise '`git fetch --tags` failed' unless $?.exitstatus.zero?
  behind = `git log ..origin/master --oneline`.split("\n").length > 0
  raise '`git log` failed' unless $?.exitstatus.zero?
  return behind
end


def youre_behind!
  if youre_behind?
    raise "You're out of sync with the remote! Try 'git pull --rebase'"
  end
end


def bump component
  youre_dirty!
  youre_behind!
  `bundle exec tony bump #{component}`
  raise '`tony bump` failed' unless $?.exitstatus.zero?
  if realm?
    `bundle exec berks`
    raise '`berks` failed' unless $?.exitstatus.zero?
    `git add Berksfile.lock`
    raise '`git add` failed' unless $?.exitstatus.zero?
  end
  version = current_version
  `git add VERSION`
  raise '`git add` failed' unless $?.exitstatus.zero?
  `git commit -m "Version bump to #{version}"`
  raise '`git commit` failed' unless $?.exitstatus.zero?
  `git tag -a v#{version} -m v#{version}`
  raise '`git tag` failed' unless $?.exitstatus.zero?
  puts 'Version is now "%s"' % version
end


def bump_and_release component=nil
  bump component unless component == :nop
  youre_dirty!
  youre_behind!
  `git push`
  raise '`git push` failed' unless $?.exitstatus.zero?
  `git push --tags`
  raise '`git push --tags` failed' unless $?.exitstatus.zero?
end


def repo_root_dir
  root_dir = `git rev-parse --show-toplevel`.strip
  raise '`git rev-parse` failed' unless $?.exitstatus.zero?
  root_dir
end


def json_files kind
  Dir[File.join(repo_root_dir, kind, '**', '*.json')]
end


def check_items kind
  json_files(kind).each do |item_path|
    item_name = File.basename(item_path, '.json')
    item_file = File.read item_path
    item      = JSON.parse item_file
    item_id   = item['id'] || item['name']
    if item_id && item_name != item_id
      raise 'Invalid %s: %s' % [ kind, item_name ]
    end
  end
end


def check_role_and_environment_naming
  cookbook_name = File.basename repo_root_dir

  env_names = json_files('environments').map { |f| File.basename(f, '.json') }

  es = env_names.select { |n| !(n =~ /^#{cookbook_name}/) }
  unless es.empty?
    raise 'Hey, I found an environment not named after the realm! (%s)' % es.join(', ')
  end

  role_names = json_files('roles').map { |f| File.basename(f, '.json') }

  rs = role_names.select { |n| !(n =~ /^#{cookbook_name}/) }
  unless rs.empty?
    raise 'Hey, I found a role not named after the realm! (%s)' % rs.join(', ')
  end

  cs = role_names & env_names

  unless cs.empty?
    raise "Hey, I found a role with the same name as an environment! (%s)" % cs.join(', ')
  end
end


def prettify_json_files
  puts 'Prettifying JSON files'
  json_files('*').each do |path|
    reformatted = JSON.pretty_generate(JSON.parse(File.read(path)))
    File.open(path, 'w') { |f| f.puts reformatted }
    `git add #{path}`
    raise '`git add` failed' unless $?.exitstatus.zero?
  end
  `git commit -m 'Prettify JSON files [automated]'`
end


def lint
  if kitchen? # skip most linting
    check_items 'data_bags'
    return
  end

  system "bundle exec knife cookbook test #{File.basename repo_root_dir} -o .."
  raise '`knife cookbook test` failed' unless $?.exitstatus.zero?
  system 'bundle exec foodcritic .' # Merely a suggestion, no "raise" here

  unless realm?
    if Dir.exist? File.join(repo_root_dir, 'environments')
      raise "Hey, I found environments, but this isn't realm!"
    end

    if Dir.exist? File.join(repo_root_dir, 'roles')
      raise "Hey, I found roles, but this isn't realm!"
    end

    if Dir.exist? File.join(repo_root_dir, 'data_bags')
      raise "Hey, I found data bags, but this isn't realm!"
    end

  else # realm
    check_items 'environments'
    check_items 'roles'
    check_items 'data_bags'
    check_role_and_environment_naming
    prettify_json_files
  end
end



JsonLint::RakeTask.new do |t|
  t.paths = %w[ **/*.json ]
end

desc 'Perform syntax check and linting'
task lint: :jsonlint do
  lint
end


if test_kitchen?
  desc 'Execute default Test Kitchen test suite'
  task test: :lint do
    system 'bundle exec kitchen test'
  end
end


if versioned?
  desc 'Print the current version'
  task :version do
    puts current_version
  end
end



if master?
  if kitchen?
    desc 'Release a Kitchen update'
    task release: %w[ lint ] do
      bump_and_release :nop
    end

  else
    namespace :release do
      desc 'Release new major version'
      task major: %w[ lint ] do
        bump_and_release :major
      end

      desc 'Release new minor version'
      task minor: %w[ lint ] do
        bump_and_release :minor
      end

      task patch: %w[ lint ] do
        bump_and_release :patch
      end
    end

    desc 'Release a new patch version'
    task release: %w[ release:patch ]
  end


  if realm?
    desc 'Apply Berksfile lock to an environment'
    task :constrain, [ :env ] do |_, args|
      youre_dirty!
      youre_behind!
      envs = json_files('environments').map { |f| File.basename(f, '.json') }
      raise 'Could not find local environment "%s"' % args[:env] unless envs.include?(args[:env])
      `git tag -a #{args[:env]} -m #{args[:env]} --force`
      raise '`git tag` failed' unless $?.exitstatus.zero?
      `git push --tag --force`
      raise '`git push` failed' unless $?.exitstatus.zero?
    end
  end
end