require 'rspec/core'  # to fix annoying "undefined method `configuration' for RSpec:Module (NoMethodError)"

require './spec/support/formatter_support'

# For Ruby 3.4.0 hash formatting
Then /^the output should print the (include|exclude)d tags {(\w+): (.*)}$/ do |word, key, value|
  if RUBY_VERSION.to_f > 3.3
    expect(all_output).to include "#{word} {#{key}: #{value}}"
  else
    expect(all_output).to include "#{word} {:#{key}=>#{value}}"
  end
end

Then /^the output should contain in either hash syntax "(.*)"$/ do |string|
  if RUBY_VERSION.to_f > 3.3
    step "the output should contain \"#{string.gsub(/:(\w+)=>/, '\1: ')}\""
  else
    step "the output should contain \"#{string}\""
  end
end

Then /^the output should contain all of these:$/ do |table|
  table.raw.flatten.each do |string|
    expect(all_output).to include(string)
  end
end

Then /^the output should not contain any of these:$/ do |table|
  table.raw.flatten.each do |string|
    expect(all_output).not_to include(string)
  end
end

Then /^the output should contain one of the following:$/ do |table|
  matching_output = table.raw.flatten.select do |string|
    all_output.include?(string)
  end

  expect(matching_output.count).to eq(1)
end

Then /^the example(?:s)? should(?: all)? pass$/ do
  step %q{the output should contain "0 failures"}
  step %q{the output should not contain "0 examples"}
  step %q{the exit status should be 0}
end

Then /^the example(?:s)? should(?: all)? fail$/ do
  step %q{the output should not contain "0 examples"}
  step %q{the output should not contain "0 failures"}
  step %q{the exit status should be 1}
  example_summary = /(\d+) examples?, (\d+) failures?/.match(all_output)
  example_count, failure_count = example_summary.captures
  expect(failure_count).to eq(example_count)
end

Then /^the process should succeed even though no examples were run$/ do
  step %q{the output should contain "0 examples, 0 failures"}
  step %q{the exit status should be 0}
end

addition_example_formatter_output = <<-EOS
Addition
  works
EOS

Then /^the output from `([^`]+)` (should(?: not)?) be in documentation format$/ do |cmd, should_or_not|
  step %Q{I run `#{cmd}`}
  step %q{the examples should all pass}
  step %Q{the output from "#{cmd}" #{should_or_not} contain "#{addition_example_formatter_output}"}
end

Then(/^the output from `([^`]+)` should indicate it ran only the subtraction file$/) do |cmd|
  step %Q{I run `#{cmd}`}
  step %q{the examples should all pass}
  step %Q{the output from "#{cmd}" should contain "1 example, 0 failures"}
  step %Q{the output from "#{cmd}" should contain "Subtraction"}
  step %Q{the output from "#{cmd}" should not contain "Addition"}
end

Then /^the backtrace\-normalized output should contain:$/ do |partial_output|
  # ruby 1.9 includes additional stuff in the backtrace,
  # so we need to normalize it to compare it with our expected output.
  normalized_output = all_output.split("\n").map do |line|
    line =~ /(^\s+# [^:]+:\d+)/ ? $1 : line # http://rubular.com/r/zDD7DdWyzF
  end.join("\n")

  expect(normalized_output).to include(partial_output)
end

Then /^the output should not contain any error backtraces$/ do
  step %q{the output should not contain "lib/rspec/core"}
end

# This step can be generalized if it's ever used to test other colors
Then /^the failing example is printed (?:wrapped )?in (.*)$/ do |color|
  code =
    case color
    when "magenta" then "\e[35m"
    when /"(.*)"/ then "\e[#{$1}m"
    end

  # \e[35m = enable magenta
  # \e[0m  = reset colors
  expect(all_output).to include(code + "F" + "\e[0m")
end

Then /^the output from `([^`]+)` should contain "(.*?)"$/  do |cmd, expected_output|
  step %Q{I run `#{cmd}`}
  step %Q{the output from "#{cmd}" should contain "#{expected_output}"}
end

Then /^the output from `([^`]+)` should not contain "(.*?)"$/  do |cmd, expected_output|
  step %Q{I run `#{cmd}`}
  step %Q{the output from "#{cmd}" should not contain "#{expected_output}"}
end

Then /^the output from `([^`]+)` should( not)? contain %R{(.*?)}$/  do |cmd, negated, expected_output|
  step %Q{I run `#{cmd}`}

  command = aruba.command_monitor.find(Aruba.platform.detect_ruby(cmd))
  regexp = Regexp.new(expected_output)

  if negated
    expect(command.output).to_not match regexp
  else
    expect(command.output).to match regexp
  end
end

Given /^I have a brand new project with no files$/ do
  cd('.') do
    expect(Dir["**/*"]).to eq([])
  end
end

Given /^I have run `([^`]*)`$/ do |cmd|
  run_command_and_stop(sanitize_text(cmd), :fail_on_error => true)
end

Given(/^a vendored gem named "(.*?)" containing a file named "(.*?)" with:$/) do |gem_name, file_name, file_contents|
  gem_dir = "vendor/#{gem_name}-1.2.3"
  step %Q{a file named "#{gem_dir}/#{file_name}" with:}, file_contents
  set_environment_variable('RUBYOPT', ENV['RUBYOPT'] + " -I#{gem_dir}/lib")
end

When('I accept the recommended settings by removing `=begin` and `=end` from `spec_helper.rb`') do
  cd('.') do
    spec_helper = File.read("spec/spec_helper.rb")
    expect(spec_helper).to include("=begin", "=end")

    to_keep = spec_helper.lines.reject do |line|
      line.start_with?("=begin") || line.start_with?("=end")
    end

    File.open("spec/spec_helper.rb", "w") { |f| f.write(to_keep.join) }
    expect(File.read("spec/spec_helper.rb")).not_to include("=begin", "=end")
  end
end

When /^I create "([^"]*)" with the following content:$/ do |file_name, content|
  write_file(file_name, content)
end

Given(/^I have run `([^`]*)` once, resulting in "([^"]*)"$/) do |command, output_snippet|
  step %Q{I run `#{command}`}
  step %Q{the output from "#{command}" should contain "#{output_snippet}"}
end

Then /^the output should contain %R{(.*)}$/ do |output_regexp|
  expect(all_output).to match(Regexp.new(output_regexp))
end

When(/^I fix "(.*?)" by replacing "(.*?)" with "(.*?)"$/) do |file_name, original, replacement|
  cd('.') do
    contents = File.read(file_name)
    expect(contents).to include(original)
    fixed = contents.sub(original, replacement)
    File.open(file_name, "w") { |f| f.write(fixed) }
  end
end

Given(/^I have not configured `example_status_persistence_file_path`$/) do
  cd('.') do
    return unless File.exist?("spec/spec_helper.rb")
    return unless File.read("spec/spec_helper.rb").include?("example_status_persistence_file_path")
    File.open("spec/spec_helper.rb", "w") { |f| f.write("") }
  end
end

Given(/^files "(.*?)" through "(.*?)" with an unrelated passing spec in each file$/) do |file1, file2|
  index_1 = Integer(file1[/\d+/])
  index_2 = Integer(file2[/\d+/])
  pattern = file1.sub(/\d+/, '%s')

  index_1.upto(index_2) do |index|
    write_file(pattern % index, <<-EOS)
      RSpec.describe "Spec file #{index}" do
        example { }
      end
    EOS
  end
end

Then(/^bisect should (succeed|fail) with output like:$/) do |succeed, expected_output|
  last_process = all_commands.last
  expected_status = succeed == "succeed" ? 0 : 1
  expect(last_process.exit_status).to eq(expected_status),
    "Expected exit status of #{expected_status} but got #{last_process.exit_status} \n\n" \
    "Output:\n\n#{last_process.stdout}"

  expected = normalize_durations(expected_output)
  actual   = normalize_durations(last_process.stdout).sub(/\n+\Z/, '')

  if !RSpec::Support::RubyFeatures.fork_supported?
    expected.gsub!('runner: :fork', 'runner: :shell')
  end

  if expected.include?("# ...")
    expected_start, expected_end = expected.split("# ...")
    expect(actual).to start_with(expected_start).and end_with(expected_end)
  else
    expect(actual).to eq(expected)
  end
end

When(/^I run `([^`]+)` and abort in the middle with ctrl\-c$/) do |cmd|
  set_environment_variable('RUBYOPT', ENV['RUBYOPT'] + " -r#{File.expand_path("../../support/send_sigint_during_bisect.rb", __FILE__)}")
  step "I run `#{cmd}`"
end

Then(/^it should fail and list all the failures:$/) do |string|
  step %q{the exit status should not be 0}
  expect(normalize_failure_output(all_output)).to include(normalize_failure_output(string))
end

Then(/^it should pass and list all the pending examples:$/) do |string|
  step %q{the exit status should be 0}
  expect(normalize_failure_output(all_output)).to include(normalize_failure_output(string))
end

Then(/^the output should report "slow before context hook" as the slowest example group$/) do
  # These expectations are trying to guard against a regression that introduced
  # this output:
  #   Top 1 slowest example groups:
  #     slow before context hook
  #       Inf seconds average (0.00221 seconds / 0 examples) RSpec::ExampleGroups::SlowBeforeContextHook::Nested
  #
  # Problems:
  # - "Inf seconds"
  # - 0 examples
  # - "Nested" group listed (it should be the outer group)
  # - The example group class name is listed (it should be the location)

  output = all_output

  expect(output).not_to match(/nested/i)
  expect(output).not_to match(/inf/i)
  expect(output).not_to match(/\b0 examples/i)

  seconds = '\d+(?:\.\d+)? seconds'

  expect(output).to match(
    %r{Top 1 slowest example groups?:\n\s+slow before context hook\n\s+#{seconds} average \(#{seconds} / 1 example\) \./spec/example_spec\.rb:1}
  )
end

Given(/^I have changed `([^`]+)` to `([^`]+)` in "(.*?)"$/) do |old_code, new_code, file_name|
  cd('.') do
    file_content = File.read(file_name)
    expect(file_content).to include(old_code)
    new_file_content = file_content.sub(old_code, new_code)
    File.open(file_name, "w") { |f| f.write(new_file_content) }
  end
end

Then /^the file `([^`]+)` should exist$/ do |file|
  cd('.') do
    File.exist?(file)
  end
end

module Normalization
  def normalize_failure_output(text)
    whitespace_normalized = text.lines.map { |line| line.sub(/\s+$/, '').sub(/:in .*$/, '') }.join

    # 1.8.7 and JRuby produce slightly different output for `Hash#fetch` errors, so we
    # convert it to the same output here to match our expectation.
    whitespace_normalized.
      sub("IndexError", "KeyError").
      sub(/key not found.*$/, "key not found")
  end
end

World(Normalization)
World(FormatterSupport)
