class ChefCLI::Policyfile::Uploader

def compat_mode_cookbook_exists_on_remote?(cookbook)

def compat_mode_cookbook_exists_on_remote?(cookbook)
  existing_cookbook_on_remote[cookbook.name.to_s]["versions"].any? do |cookbook_info|
    cookbook_info["version"] == cookbook.version
  end
end

def cookbook_versions_for_policy

the policyfile lock requires.
An Array of Chef::CookbookVersion objects representing the full set that
def cookbook_versions_for_policy
  return @cookbook_versions_for_policy if @cookbook_versions_for_policy
  policyfile_lock.validate_cookbooks!
  @cookbook_versions_for_policy =
    if using_policy_document_native_api?
      load_cookbooks_in_native_mode
    else
      load_cookbooks_in_compat_mode
    end
end

def cookbook_versions_to_upload

def cookbook_versions_to_upload
  cookbook_versions_for_policy.inject([]) do |versions_to_upload, cookbook_with_lock|
    cb = cookbook_with_lock.cookbook
    # When we abandon custom identifier support in favor of the one true
    # hash, identifier generation code can be moved into chef proper and
    # this can be removed.
    cb.identifier = cookbook_with_lock.lock.identifier
    versions_to_upload << cb unless remote_already_has_cookbook?(cb)
    versions_to_upload
  end
end

def data_bag_create

def data_bag_create
  http_client.post("data", { "name" => COMPAT_MODE_DATA_BAG_NAME })
rescue Net::HTTPServerException => e
  raise e unless e.response.code == "409"
end

def data_bag_item_create

def data_bag_item_create
  policy_id = "#{policy_name}-#{policy_group}"
  lock_data = policy_lock_for_transport.dup
  lock_data["id"] = policy_id
  data_item = {
    "id" => policy_id,
    "name" => "data_bag_item_#{COMPAT_MODE_DATA_BAG_NAME}_#{policy_id}",
    "data_bag" => COMPAT_MODE_DATA_BAG_NAME,
    "raw_data" => lock_data,
    # we'd prefer to leave this out, but the "compatibility mode"
    # implementation in chef-client relies on magical class inflation
    "json_class" => "Chef::DataBagItem",
  }
  upload_lockfile_as_data_bag_item(policy_id, data_item)
  ui.msg("Policy uploaded as data bag item #{COMPAT_MODE_DATA_BAG_NAME}/#{policy_id}")
  true
end

def existing_cookbook_on_remote

def existing_cookbook_on_remote
  @existing_cookbook_on_remote ||= http_client.get(list_cookbooks_url)
end

def initialize(policyfile_lock, policy_group, ui: nil, http_client: nil, policy_document_native_api: false)

def initialize(policyfile_lock, policy_group, ui: nil, http_client: nil, policy_document_native_api: false)
  @policyfile_lock = policyfile_lock
  @policy_group = policy_group
  @http_client = http_client
  @ui = ui || UI.null
  @policy_document_native_api = policy_document_native_api
  @policy_lock_for_transport = nil
  @cookbook_versions_for_policy = nil
end

def list_cookbooks_url

def list_cookbooks_url
  if using_policy_document_native_api?
    "cookbook_artifacts?num_versions=all"
  else
    "cookbooks?num_versions=all"
  end
end

def load_cookbooks_in_compat_mode

def load_cookbooks_in_compat_mode
  policyfile_lock.cookbook_locks.map do |name, lock|
    cb = ReadCookbookForCompatModeUpload.load(name, lock.dotted_decimal_identifier, lock.cookbook_path)
    LockedCookbookForUpload.new(cb, lock)
  end
end

def load_cookbooks_in_native_mode

def load_cookbooks_in_native_mode
  policyfile_lock.cookbook_locks.map do |name, lock|
    cb = CookbookLoaderWithChefignore.load(name, lock.cookbook_path)
    LockedCookbookForUpload.new(cb, lock)
  end
end

def native_mode_cookbook_exists_on_remote?(cookbook)

def native_mode_cookbook_exists_on_remote?(cookbook)
  existing_cookbook_on_remote[cookbook.name.to_s]["versions"].any? do |cookbook_info|
    cookbook_info["identifier"] == cookbook.identifier
  end
end

def policy_lock_for_transport

def policy_lock_for_transport
  @policy_lock_for_transport ||= policyfile_lock.to_lock
end

def policy_name

def policy_name
  policyfile_lock.name
end

def remote_already_has_cookbook?(cookbook)

def remote_already_has_cookbook?(cookbook)
  return false unless existing_cookbook_on_remote.key?(cookbook.name.to_s)
  if using_policy_document_native_api?
    native_mode_cookbook_exists_on_remote?(cookbook)
  else
    compat_mode_cookbook_exists_on_remote?(cookbook)
  end
end

def short_revision_id

def short_revision_id
  policy_lock_for_transport["revision_id"][0, 10]
end

def upload

def upload
  ui.msg("Uploading policy #{policy_name} (#{short_revision_id}) to policy group #{policy_group}")
  unless using_policy_document_native_api?
    ui.msg(<<~DRAGONS)
      WARN: Uploading policy to policy group #{policy_group} in compatibility mode.
      Cookbooks will be uploaded with very large version numbers, which may be picked
      up by existing nodes.
    DRAGONS
  end
  upload_cookbooks
  upload_policy
end

def upload_cookbooks

def upload_cookbooks
  ui.msg("WARN: Uploading cookbooks using semver compat mode") unless using_policy_document_native_api?
  uploader.upload_cookbooks unless cookbook_versions_to_upload.empty?
  reused_cbs, uploaded_cbs = cookbook_versions_for_policy.partition do |cb_with_lock|
    remote_already_has_cookbook?(cb_with_lock.cookbook)
  end
  Reports::Upload.new(reused_cbs: reused_cbs, uploaded_cbs: uploaded_cbs, ui: ui).show
  true
end

def upload_lockfile_as_data_bag_item(policy_id, data_item)

def upload_lockfile_as_data_bag_item(policy_id, data_item)
  http_client.put("data/#{COMPAT_MODE_DATA_BAG_NAME}/#{policy_id}", data_item)
rescue Net::HTTPServerException => e
  raise e unless e.response.code == "404"
  http_client.post("data/#{COMPAT_MODE_DATA_BAG_NAME}", data_item)
end

def upload_policy

def upload_policy
  if using_policy_document_native_api?
    upload_policy_native
  else
    data_bag_create
    data_bag_item_create
  end
end

def upload_policy_native

def upload_policy_native
  http_client.put("/policy_groups/#{policy_group}/policies/#{policy_name}", policy_lock_for_transport)
end

def uploader

def uploader
  # TODO: uploader runs cookbook validation; we want to do this at a different time.
  @uploader ||= Chef::CookbookUploader.new(cookbook_versions_to_upload, rest: http_client, policy_mode: using_policy_document_native_api?)
end

def using_policy_document_native_api?

def using_policy_document_native_api?
  @policy_document_native_api
end