# frozen_string_literal: true

require 'fileutils'
require 'pathname'
require 'log4r'
require 'json'
module VagrantPlugins
  module ProviderLocal
    module Action
      # This is used to package the VM into a box
      class Package
        def initialize(app, env)
          @logger = Log4r::Logger.new('vagrant_local::action::import')
          @app = app
          @executor = Executor::Exec.new
          @pfexec = if Process.uid.zero?
                      ''
                    elsif system('sudo -v')
                      'sudo'
                    else
                      'pfexec'
                    end
          env['package.output'] ||= 'package.box'
        end

        def execute(...)
          @executor.execute(...)
        end

        def call(env)
          boxname = env['package.output']
          mpc = env[:machine].provider_config
          files = {}
          raise "#{boxname}: Already exists" if File.exist?(boxname)

          ## Create Snapshot
          FileUtils.mkdir_p("#{Dir.pwd}/_tmp_package")
          datasetpath = "#{mpc.boot['array']}/#{mpc.boot['dataset']}/#{env[:machine].name}"
          datetime = Time.new.strftime('%Y-%m-%d-%H:%M:%S')
          snapshot_create(datasetpath, datetime, env[:ui], mpc)
          snapshot_send(datasetpath, "#{Dir.pwd}/_tmp_package/box.zss", datetime, env[:ui], mpc)

          ## Include User Extra Files
          env['package.include'].each do |file|
            source = Pathname.new(file)
            dest = if source.relative?
                     source
                   else
                     source.basename
                   end
            files[file] = dest
          end

          # Verify the mapping
          files.each_key do |from|
            raise Vagrant::Errors::PackageIncludeMissing, file: from unless File.exist?(from)
          end

          files.each do |from, dest|
            include_directory = Pathname.new("#{Dir.pwd}/_tmp_package/")
            to = include_directory.join(dest)
            FileUtils.mkdir_p(to.parent)
            if File.directory?(from)
              FileUtils.cp_r(Dir.glob(from), to.parent, preserve: true)
            else
              FileUtils.cp(from, to, preserve: true)
            end
          end

          ## Create a Vagrantfile or load from Users Defined File
          vagrantfile_content = <<~'CODE'
            require 'yaml'
            require_relative 'Hosts'
            settings = YAML::load(File.read("#{File.dirname(__FILE__)}/Hosts.yml"))
            Vagrant.configure("2") do |config|
              Hosts.configure(config, settings)
            end
          CODE
          File.write("#{Dir.pwd}/_tmp_package/Vagrantfile", vagrantfile_content)
          files[env['package.vagrantfile']] = '_Vagrantfile' if env['package.vagrantfile']

          info_content_hash = {
            'boxname' => boxname,
            'Author' => mpc.vagrant_cloud_creator,
            'vagrant-local' => 'This box was built with vagrant-local: https://github.com/STARTcloud/vagrant-local'
          }
          File.write("#{Dir.pwd}/_tmp_package/info.json", info_content_hash.to_json)

          metadata_content_hash = {
            'provider' => 'local',
            'architecture' => 'amd64',
            'brand' => mpc.brand,
            'format' => 'zss',
            'url' => "https://app.vagrantup.com/#{mpc.vagrant_cloud_creator}/boxes/#{mpc.boxshortname}"
          }
          metadata_content_hash['kernel'] = mpc.kernel if !mpc.kernel.nil? && mpc.kernel != false
          File.write("#{Dir.pwd}/_tmp_package/metadata.json", metadata_content_hash.to_json)

          ## Create the Box file
          assemble_box(boxname, "#{Dir.pwd}/_tmp_package")
          FileUtils.rm_rf("#{Dir.pwd}/_tmp_package")

          env[:ui].info("Box created, You can now add the box: 'vagrant box add #{boxname} --name newbox'")
          @app.call(env)
        end

        def snapshot_create(datasetpath, datetime, uii, config)
          uii.info('Creating a Snapshot of the box.')
          result = execute(true, "#{@pfexec} zfs snapshot -r #{datasetpath}/boot@vagrant_box#{datetime}")
          uii.info("#{@pfexec} zfs snapshot -r #{datasetpath}/boot@vagrant_box#{datetime}") if result.zero? && config.debug
        end

        def snapshot_delete(datasetpath, uii, datetime)
          result = execute(true, "#{@pfexec} zfs destroy -r -f #{datasetpath}/boot@vagrant_box#{datetime}")
          uii.info("#{@pfexec} zfs destroy -r -f #{datasetpath}/boot@vagrant_box#{datetime}") if result.zero? && config.debug
        end

        def snapshot_send(datasetpath, destination, datetime, uii, config)
          uii.info('Sending Snapshot to ZFS Send Stream image.')
          result = execute(true, "#{@pfexec} zfs send #{datasetpath}/boot@vagrant_box#{datetime} > #{destination}")
          uii.info("#{@pfexec} zfs send -r #{datasetpath}/boot@vagrant_box#{datetime} > #{destination}") if result.zero? && config.debug
        end

        def assemble_box(boxname, tmp_dir)
          is_linux = `bash -c '[[ "$(uname -a)" =~ "Linux" ]]'`
          Dir.chdir(tmp_dir) do
            files = Dir.glob(File.join('.', '*'))
            tar_command = is_linux ? 'tar -cvzf' : 'tar -cvzEf'
            `#{tar_command} ../#{boxname} #{files.join(' ')}`
          end
        end
      end
    end
  end
end