require "digest/md5"
require "securerandom"
require "set"

require "log4r"

require "vagrant/util/counter"

require_relative "chef_solo"

module VagrantPlugins
  module Chef
    module Provisioner
      # This class implements provisioning via chef-zero.
      class ChefZero < ChefSolo
        def initialize(machine, config)
          super
          @logger = Log4r::Logger.new("vagrant::provisioners::chef_zero")
        end

        def provision
          install_chef
          # Verify that the proper shared folders exist.
          check = []
          @shared_folders.each do |type, local_path, remote_path|
            # We only care about checking folders that have a local path, meaning
            # they were shared from the local machine, rather than assumed to
            # exist on the VM.
            check << remote_path if local_path
          end

          chown_provisioning_folder
          verify_shared_folders(check)
          verify_binary(chef_binary_path("chef-client"))
          upload_encrypted_data_bag_secret
          setup_json
          setup_zero_config
          run_chef_zero
          delete_encrypted_data_bag_secret
        end

        def setup_zero_config
          setup_config("provisioners/chef_zero/zero", "client.rb", {
            local_mode: true,
            enable_reporting: false,
            cookbooks_path: guest_paths(@cookbook_folders),
            roles_path: guest_paths(@role_folders),
            data_bags_path: guest_paths(@data_bags_folders).first,
            environments_path: guest_paths(@environments_folders).first,
          })
        end

        def run_chef_zero
          if @config.run_list && @config.run_list.empty?
            @machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
          end

          if @machine.guest.capability?(:wait_for_reboot)
            @machine.guest.capability(:wait_for_reboot)
          end

          command = CommandBuilder.command(:client, @config,
            windows:    windows?,
            colored:    @machine.env.ui.color?,
            local_mode: true,
          )

          @config.attempts.times do |attempt|
            if attempt == 0
              @machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero")
            else
              @machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero_again")
            end

            opts = { error_check: false, elevated: true }
            exit_status = @machine.communicate.sudo(command, opts) do |type, data|
              # Output the data with the proper color based on the stream.
              color = type == :stdout ? :green : :red

              data = data.chomp
              next if data.empty?

              @machine.ui.info(data, color: color)
            end

            # There is no need to run Chef again if it converges
            return if exit_status == 0
          end

          # If we reached this point then Chef never converged! Error.
          raise ChefError, :no_convergence
        end

        def verify_shared_folders(folders)
          folders.each do |folder|
            @logger.debug("Checking for shared folder: #{folder}")
            if !@machine.communicate.test("test -d #{folder}", sudo: true)
              raise ChefError, :missing_shared_folders
            end
          end
        end

        protected

        # Extracts only the remote paths from a list of folders
        def guest_paths(folders)
          folders.map { |parts| parts[2] }
        end
      end
    end
  end
end
