include ::PackageCloud::Helper

require 'uri'

use_inline_resources if defined?(use_inline_resources)

action :add do
  case new_resource.type
  when 'deb'
    install_deb
  when 'rpm'
    install_rpm
  when 'gem'
    install_gem
  else
    raise "#{new_resource.type} is an unknown package type."
  end
end

def install_deb
  base_url = new_resource.base_url
  repo_url = construct_uri_with_options({base_url: base_url, repo: new_resource.repository, endpoint: node['platform']})

  Chef::Log.debug("#{new_resource.name} deb repo url = #{repo_url}")

  package 'apt-transport-https'

  template "/etc/apt/sources.list.d/#{filename}.list" do
    source 'apt.erb'
    cookbook 'packagecloud'
    mode '0644'
    variables :base_url     => read_token(repo_url).to_s,
              :distribution => node['lsb']['codename'],
              :component    => 'main'

    notifies :run, "execute[apt-key-add-#{filename}]", :immediately
    notifies :run, "execute[apt-get-update-#{filename}]", :immediately
  end

  gpg_key_url = ::File.join(base_url, node['packagecloud']['gpg_key_path'])

  execute "apt-key-add-#{filename}" do
    command "wget -qO - #{gpg_key_url} | apt-key add -"
    action :nothing
  end

  execute "apt-get-update-#{filename}" do
    command "apt-get update -o Dir::Etc::sourcelist=\"sources.list.d/#{filename}.list\"" \
            " -o Dir::Etc::sourceparts=\"-\"" \
            " -o APT::Get::List-Cleanup=\"0\""
    action :nothing
  end
end

def install_rpm
  given_base_url = new_resource.base_url

  base_repo_url = ::File.join(given_base_url, node['packagecloud']['base_repo_path'])

  base_url_endpoint = construct_uri_with_options({base_url: base_repo_url, repo: new_resource.repository, endpoint: 'rpm_base_url'})

  gpg_filename = URI.parse(base_repo_url).host.gsub!('.', '_')

  if new_resource.master_token
    base_url_endpoint.user     = new_resource.master_token
    base_url_endpoint.password = ''
  end

  base_url = URI(get(base_url_endpoint, install_endpoint_params).body.chomp)

  Chef::Log.debug("#{new_resource.name} rpm base url = #{base_url}")

  package 'pygpgme' do
    ignore_failure true
  end

  log 'pygpgme_warning' do
    message 'The pygpgme package could not be installed. This means GPG verification is not possible for any RPM installed on your system. ' \
            'To fix this, add a repository with pygpgme. Usualy, the EPEL repository for your system will have this. ' \
            'More information: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F and https://github.com/opscode-cookbooks/yum-epel'

    level :warn
    not_if 'rpm -qa | grep -qw pygpgme'
  end

  ruby_block 'disable repo_gpgcheck if no pygpgme' do
    block do
      template = run_context.resource_collection.find(:template => "/etc/yum.repos.d/#{filename}.repo")
      template.variables[:repo_gpgcheck] = 0
    end
    not_if 'rpm -qa | grep -qw pygpgme'
  end

  remote_file "/etc/pki/rpm-gpg/RPM-GPG-KEY-#{gpg_filename}" do
    source ::File.join(given_base_url, node['packagecloud']['gpg_key_path'])
    mode '0644'
  end

  template "/etc/yum.repos.d/#{filename}.repo" do
    source 'yum.erb'
    cookbook 'packagecloud'
    mode '0644'
    variables :base_url        => read_token(base_url).to_s,
              :gpg_filename  => gpg_filename,
              :name            => filename,
              :repo_gpgcheck   => 1,
              :description     => filename,
              :priority        => new_resource.priority,
              :metadata_expire => new_resource.metadata_expire

    notifies :run, "execute[yum-makecache-#{filename}]", :immediately
    notifies :create, "ruby_block[yum-cache-reload-#{filename}]", :immediately
  end

  # get the metadata for this repo only
  execute "yum-makecache-#{filename}" do
    command "yum -q makecache -y --disablerepo=* --enablerepo=#{filename}"
    action :nothing
  end

  # reload internal Chef yum cache
  ruby_block "yum-cache-reload-#{filename}" do
    block { Chef::Provider::Package::Yum::YumCache.instance.reload }
    action :nothing
  end
end

def install_gem
  base_url = new_resource.base_url

  repo_url = construct_uri_with_options({base_url: base_url, repo: new_resource.repository})
  repo_url = read_token(repo_url, true).to_s


  execute "install packagecloud #{new_resource.name} repo as gem source" do
    command "gem source --add #{repo_url}"
    not_if "gem source --list | grep #{repo_url}"
  end
end

def read_token(repo_url, gems=false)
  return repo_url unless new_resource.master_token

  base_url = new_resource.base_url

  base_repo_url = ::File.join(base_url, node['packagecloud']['base_repo_path'])

  uri = construct_uri_with_options({base_url: base_repo_url, repo: new_resource.repository, endpoint: 'tokens.text'})
  uri.user     = new_resource.master_token
  uri.password = ''

  resp = post(uri, install_endpoint_params)

  Chef::Log.debug("#{new_resource.name} TOKEN = #{resp.body.chomp}")

  if is_rhel5? && !gems
    repo_url
  else
    repo_url.user     = resp.body.chomp
    repo_url.password = ''
    repo_url
  end
end

def install_endpoint_params
  dist = value_for_platform_family(
    'debian' => node['lsb']['codename'],
    ['rhel', 'fedora'] => node['platform_version'],
  )

  if node['fqdn'].nil?
    Chef::Log.fatal("This node's fqdn is set to nil, so a read token cannot be issued!" \
                    "Please change your fqdn settings.")
  end

  { :os   => node['platform'],
    :dist => dist,
    :name => node['fqdn'] }
end

def filename
  new_resource.name.gsub(/[^0-9A-z.\-]/, '_')
end

def is_rhel5?
  platform_family?('rhel') && node['platform_version'].to_i == 5
end

def construct_uri_with_options(options)
  required_options = [:base_url, :repo]

  required_options.each do |opt|
    if !options[opt]
      raise ArgumentError,
            "A required option :#{opt} was not specified"
    end
  end

  options[:base_url] = append_trailing_slash(options[:base_url])
  options[:repo]     = append_trailing_slash(options[:repo])

  URI.join(options.delete(:base_url), options.inject([]) {|mem, opt| mem << opt[1]}.join)
end

def append_trailing_slash(str)
  str.end_with?("/") ? str : str << "/"
end
