# frozen_string_literal: true
require 'open3'
module Spec
  module Helpers
    def self.bang(method)
      define_method("#{method}!") do |*args, &blk|
        send(method, *args, &blk).tap do
          if exitstatus && exitstatus != 0
            error = out + "\n" + err
            error.strip!
            raise RuntimeError,
              "Invoking #{method}!(#{args.map(&:inspect).join(", ")}) failed:\n#{error}",
              caller.drop_while {|bt| bt.start_with?(__FILE__) }
          end
        end
      end
    end

    attr_reader :out, :err, :exitstatus

    def lib
      File.expand_path("../../../lib", __FILE__)
    end

    def bundle(cmd, options = {})
      with_sudo = options.delete(:sudo)
      sudo = with_sudo == :preserve_env ? "sudo -E" : "sudo" if with_sudo

      options["no-color"] = true unless options.key?("no-color") || cmd.to_s =~ /\A(e|ex|exe|exec|conf|confi|config)(\s|\z)/

      bundle_bin = options.delete("bundle_bin") || File.expand_path("../../../exe/bundle", __FILE__)

      if system_bundler = options.delete(:system_bundler)
        bundle_bin = "-S bundle"
      end

      requires = options.delete(:requires) || []
      # if artifice = options.delete(:artifice) { "fail" unless RSpec.current_example.metadata[:realworld] }
      #  requires << File.expand_path("../artifice/#{artifice}.rb", __FILE__)
      # end
      # requires << "support/hax"
      requires_str = requires.map {|r| "-r#{r}" }.join(" ")

      load_path = []
      load_path << lib unless system_bundler
      load_path << File.expand_path("../../../spec", __FILE__)
      load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}"

      env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}'" }.join(" ")
      env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler
      args = options.map do |k, v|
        v == true ? " --#{k}" : " --#{k} #{v}" if v
      end.join

      cmd = "#{env} #{sudo} #{Gem.ruby} #{load_path_str} #{requires_str} #{bundle_bin} #{cmd}#{args}"
      sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? }
    end
    bang :bundle

    def sys_exec(cmd)
      Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr|
        yield stdin, stdout, wait_thr if block_given?
        stdin.close

        @exitstatus = wait_thr && wait_thr.value.exitstatus
        @out = Thread.new { stdout.read }.value.strip
        @err = Thread.new { stderr.read }.value.strip
      end

      (@all_output ||= String.new) << [
        "$ #{cmd.to_s.strip}",
        out,
        err,
        @exitstatus ? "# $? => #{@exitstatus}" : "",
        "\n",
      ].reject(&:empty?).join("\n")

      @out
    end
    bang :sys_exec
  end
end
