# frozen_string_literal: true

require 'simplecov-cobertura'
require 'tzinfo'
require 'tmpdir'
require 'tempfile'
require 'webmock/rspec'

SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
  SimpleCov::Formatter::SimpleFormatter,
  SimpleCov::Formatter::HTMLFormatter,
  SimpleCov::Formatter::CoberturaFormatter
])
SimpleCov.start

require_relative '../lib/gdk_src'
require_relative '../lib/gdk'
require_relative '../lib/gdk/task_helpers'

require 'rake'
require "active_support/concern"

# Autoload helpers
autoload :MeasureHelper, 'helpers/measure_helper'
autoload :ShelloutHelper, 'helpers/shellout_helper'

# Load spec support code
Dir['spec/spec_support/**/*.rb'].each { |f| load f }

RSpec.configure do |config|
  # Allow running `:focus` examples locally, run everything on CI.
  config.filter_run_when_matching :focus unless ENV['CI']

  config.example_status_persistence_file_path = 'spec/examples.txt'
  config.disable_monkey_patching!
  config.order = :random
  Kernel.srand config.seed

  config.before(:suite) do
    temp_path.glob('*').each(&:rmtree)
  end

  config.before do |example|
    if example.metadata[:hide_stdout]
      allow(GDK::Output).to receive(:print)
      allow(GDK::Output).to receive(:puts)
    end

    if example.metadata[:hide_output]
      allow(GDK::Output).to receive(:print)
      allow(GDK::Output).to receive(:puts)
      allow(GDK::Output).to receive(:info)
      allow(GDK::Output).to receive(:warn)
      allow(GDK::Output).to receive(:error)
      allow(GDK::Output).to receive(:abort)
      allow(GDK::Output).to receive(:success)
    end

    unless example.metadata[:gdk_root]
      # isolate configs for the testing environment
      stub_const('GDK::Config::GDK_ROOT', Pathname.new('/home/git/gdk'))
      stub_const('GDK::Config::FILE', 'gdk.example.yml')

      gdk_root_tmp_path = temp_path

      real_tool_versions_file = Pathname.new('.tool-versions').expand_path
      allow(gdk_root_tmp_path).to receive(:join).and_call_original
      allow(gdk_root_tmp_path).to receive(:join).with('.tool-versions').and_return(real_tool_versions_file)
      allow(gdk_root_tmp_path).to receive(:glob).with('{.tool-versions,{*,*/*}/.tool-versions}').and_return([real_tool_versions_file])

      allow(GDK).to receive(:root).and_return(gdk_root_tmp_path)
      stub_gdk_yaml({})

      allow(Utils).to receive(:find_executable).and_return(nil)
    end

    unless example.metadata[:with_telemetry]
      allow(GDK::Telemetry).to receive(:with_telemetry).and_wrap_original do |_method, *_args, &block|
        block.call
      end
      allow(GDK::Telemetry).to receive(:capture_exception).with(anything)
      allow(GDK::Telemetry).to receive(:send_telemetry)
      allow(GDK::Telemetry).to receive(:flush_events)
    end
  end
end

def utc_now
  TZInfo::Timezone.get('UTC').now
end

def freeze_time(&blk)
  travel_to(&blk)
end

def travel_to(now = utc_now)
  # Copied from https://github.com/rails/rails/blob/v6.1.3/activesupport/lib/active_support/testing/time_helpers.rb#L163-L165
  #
  allow(Time).to receive(:now).and_return(now)
  allow(Date).to receive(:today).and_return(Date.jd(now.to_date.jd))
  allow(DateTime).to receive(:now).and_return(DateTime.jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)))

  yield

  allow(Time).to receive(:now).and_call_original
  allow(Date).to receive(:today).and_call_original
  allow(DateTime).to receive(:now).and_call_original
end

def fixture_path
  GDK::SRC.join('spec').join('fixtures')
end

def temp_path
  GDK::SRC.join('tmp')
end

def stub_env(var, return_value)
  stub_const('ENV', ENV.to_hash.merge(var => return_value))
end

def stub_gdk_yaml(yaml)
  yaml = YAML.safe_load(yaml) if yaml.is_a?(String)
  config = GDK::Config.new(yaml: yaml)
  allow(GDK).to receive(:config) { config }
end

def stub_persisted_gdk_yaml(yaml)
  config = GDK::Config.load_from_file
  config.instance_variable_set(:@yaml, yaml)
  allow(GDK).to receive(:config) { config }
end

def stub_raw_gdk_yaml(raw_yaml)
  allow(File).to receive(:read).and_call_original
  allow(File).to receive(:read).with(GDK::Config::FILE).and_return(raw_yaml)
  allow(GDK).to receive(:config).and_call_original
end

def stub_pg_bindir
  allow_any_instance_of(GDK::PostgresqlUpgrader).to receive(:bin_path).and_return('/usr/local/bin')
end

def stub_tty(state)
  allow($stdin).to receive(:isatty).and_return(state)
end

def stub_no_color_env(res)
  stub_tty(true)

  # res needs to be of type String as we're simulating what's coming from
  # the shell command line.
  stub_env('NO_COLOR', res)
end

def stub_backup
  instance_spy(GDK::Backup).tap do |b|
    allow(GDK::Backup).to receive(:new).and_return(b)
  end
end

def stub_gdk_debug(state)
  gdk_settings = double('GDK::ConfigSettings', debug?: state, __debug?: state) # rubocop:todo RSpec/VerifiedDoubles
  allow_any_instance_of(GDK::Config).to receive(:gdk).and_return(gdk_settings)
end

def stub_prompt(response, message = 'Are you sure? [y/N]')
  allow(GDK::Output).to receive(:interactive?).and_return(true)
  allow(GDK::Output).to receive(:prompt).with(message).and_return(response)
end

def stub_tool_versions
  pg_version = instance_double(Asdf::ToolVersion, name: 'postgres', version: '16.8')
  pg_tool = instance_double(Asdf::Tool, name: pg_version.name, default_tool_version: pg_version)
  tools = {
    pg_version.name => pg_tool
  }

  allow_any_instance_of(Asdf::ToolVersions).to receive(:tool_versions).and_return(tools)
end

def stub_find_executable(name, path)
  allow(Utils).to receive(:find_executable).with(name).and_return(path)
end

def unstub_find_executable
  allow(Utils).to receive(:find_executable).and_call_original
end

def expect_no_error_report
  expect(GDK::Telemetry).not_to have_received(:capture_exception)
end

def create_dummy_executable(name)
  path = File.join(tmp_path, name)
  FileUtils.touch(path)
  File.chmod(0o755, path)

  path
end
