# Load test harness - Minitest
require "minitest/autorun"
require "minitest/pride"

# Data formats commonly used in testing
require "json" unless defined?(JSON)
require "ostruct" unless defined?(OpenStruct)

# Utilities often needed
require "fileutils" unless defined?(FileUtils)
require "tmpdir" unless defined?(Dir.mktmpdir)
require "pathname" unless defined?(Pathname)
require "forwardable" unless defined?(Forwardable)

require "functional/helper"
require "inspec/plugin/v2"

# Configure Minitest to expose things like `let`
class Module
  include Minitest::Spec::DSL # TODO: NO! remove this!
end

# module Inspec
#   class FuncTestRunResult
#     attr_reader :train_result
#     attr_reader :payload
#
#     extend Forwardable
#     def_delegator :train_result, :stdout
#     def_delegator :train_result, :stderr
#     def_delegator :train_result, :exit_status
#
#     def initialize(train_result)
#       @train_result = train_result
#       @payload = OpenStruct.new
#     end
#   end
# end

# TODO: remove me! There's no need!
module CorePluginBaseHelper
  let(:mock_path) { File.join(repo_path, "test", "fixtures", "mock") }
  let(:core_fixture_plugins_path) { File.join(mock_path, "plugins") }
  let(:core_config_dir_path) { File.join(mock_path, "config_dirs") }

  let(:registry) { Inspec::Plugin::V2::Registry.instance }
end

module CorePluginFunctionalHelper
  include CorePluginBaseHelper
  include FunctionalHelper

  # This helper does some fancy footwork to make InSpec think a plugin
  # under development is temporarily installed.
  # @param String command_line Invocation, without the word 'inspec'
  # @param Hash opts options as for run_inspec_process, with more options:
  #    :pre_run: Proc(plugin_statefile_data, tmp_dir_path) - optional setup block.
  #       Modify plugin_statefile_data as needed; it will be written to a plugins.json
  #       in tmp_dir_path.  You may also copy in other things to the tmp_dir_path. Your PWD
  #       will be in the tmp_dir, and it will exist and be empty.
  #    :post_run: Proc(CommandResult, tmp_dir_path) - optional result capture block.
  #       Your PWD will be the tmp_dir, and it will still exist (for a moment!)
  def run_inspec_process_with_this_plugin(command_line, opts = {})
    plugin_path = __find_plugin_path_from_caller

    # If it looks like it is a core plugin under test, don't add it to the plugin file
    # since the loader will auto-load it anyway
    if plugin_path.include?("lib/plugins/inspec-")
      plugin_file_data = __make_empty_plugin_file_data_structure
    else
      plugin_file_data = __make_plugin_file_data_structure_with_path(plugin_path)
    end

    Dir.mktmpdir do |tmp_dir|
      opts[:pre_run]&.call(plugin_file_data, tmp_dir)
      plugin_file_path = File.join(tmp_dir, "plugins.json")
      # HACK: If the block cleared the hash, take that to mean it will provide a plugins.json file of its own.
      File.write(plugin_file_path, JSON.generate(plugin_file_data)) unless plugin_file_data.empty?
      opts[:env] ||= {}
      opts[:env]["INSPEC_CONFIG_DIR"] = tmp_dir
      run_result = run_inspec_process(command_line, opts)

      # Read the resulting plugins.json into memory, if any
      if File.exist?(plugin_file_path)
        @plugin_data = JSON.parse(File.read(plugin_file_path))
      end

      opts[:post_run]&.call(run_result, tmp_dir)
      run_result
    end
  end

  def __find_plugin_path_from_caller(frames_back = 2)
    caller_path = Pathname.new(caller_locations(frames_back, 1).first.absolute_path)
    # Typical caller path:
    # /Users/cwolfe/sandbox/inspec-resource-lister/test/functional/inspec_resource_lister_test.rb
    # We want:
    # /Users/cwolfe/sandbox/inspec-resource-lister/lib/inspec-resource-lister.rb
    cursor = caller_path
    until cursor.basename.to_s == "test" && cursor.parent.basename.to_s =~ /^(inspec|train)-/
      cursor = cursor.parent
      break if cursor.nil?
    end
    raise "Could not comprehend plugin project directory structure" if cursor.nil?

    project_dir = cursor.parent
    plugin_name = project_dir.basename
    entry_point = File.join(project_dir.to_s, "lib", plugin_name.to_s + ".rb")
    raise "Could not find plugin entry point" unless File.exist?(entry_point)

    entry_point
  end

  def __make_plugin_file_data_structure_with_path(path)
    # TODO: dry this up, refs #3350
    plugin_name = File.basename(path, ".rb")
    data = __make_empty_plugin_file_data_structure
    data["plugins"] << {
      "name" => plugin_name,
      "installation_type" => "path",
      "installation_path" => path,
    }
    data
  end

  def __make_empty_plugin_file_data_structure
    # TODO: dry this up, refs #3350
    {
      "plugins_config_version" => "1.0.0",
      "plugins" => [],
    }
  end
end

module CorePluginUnitHelper
  include CorePluginBaseHelper
end
