# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

require "fileutils"
require "pathname"

require "log4r"
require "childprocess"

require "vagrant-spec/isolated_environment"
require "vagrant-spec/subprocess"
require "vagrant-spec/which"

module Vagrant
  module Spec
    # This class extends the normal IsolatedEnvironment to add some
    # additional helpers for executing applications within that environment.
    class AcceptanceIsolatedEnvironment < IsolatedEnvironment
      def initialize(apps: nil, env: nil, skeleton_paths: nil)
        super()

        @logger = Log4r::Logger.new("test::acceptance::isolated_environment")

        @apps = (apps || {}).dup
        @env  = (env || {}).dup
        @skeleton_paths = (skeleton_paths || []).map do |path|
          Pathname.new(path)
        end

        # Set the home directory for any apps we execute
        @env["HOME"] = @homedir.to_s
        @env["VAGRANT_HOME"] = @homedir.to_s

        # Replace some special variables
        @env.each do |k, v|
          @env[k] = @homedir.to_s if v == "{{homedir}}"
        end
      end

      # Executes a command in the context of this isolated environment.
      # Any command executed will therefore see our temporary directory
      # as the home directory.
      #
      # If the command has been defined with a special path, then the
      # command will be replaced with the full path to that command.
      def execute(command, *args, **options)
        # Create the command
        command = replace_command(command)
        # Use provided command if it is a valid executable
        if !File.executable?(command)
          # If it's not a valid executable, search for vagrant
          command = Which.which(command)
        end

        # Build up the options
        options[:env] = @env
        options[:notify] = [:stdin, :stderr, :stdout]
        options[:workdir] = @workdir.to_s

        # Execute, logging out the stdout/stderr as we get it
        @logger.info("Executing: #{[command].concat(args).inspect}")
        Subprocess.execute(command, *args, **options) do |type, data|
          @logger.debug("#{type}: #{data}") if type == :stdout || type == :stderr
          yield type, data if block_given?
        end
      end

      # Copies the skeleton directory into the working directory.
      #
      # @param [String] name Name of the skeleton to copy.
      def skeleton(name)
        path = @skeleton_paths.find do |p|
          p.join(name).directory?
        end

        if !path
          raise ArgumentError,
            "Skeleton '#{name}' not found in any directory."
        end

        path.join(name).children.each do |c|
          FileUtils.cp_r(c, @workdir.to_s)
        end
      end

      protected

      # This replaces a command with a replacement defined when this
      # isolated environment was initialized. If nothing was defined,
      # then the command itself is returned.
      def replace_command(command)
        return @apps[command] if @apps.has_key?(command)
        return command
      end
    end
  end
end
