require 'r10k/module'
require 'r10k/errors'
require 'r10k/logging'

require 'fileutils'
require 'systemu'
require 'semver'
require 'json'

class R10K::Module::Forge < R10K::Module::Base

  R10K::Module.register(self)

  def self.implement?(name, args)
    !!(name.match %r[\w+/\w+])
  end

  include R10K::Logging

  attr_accessor :owner, :full_name

  def initialize(name, basedir, args)
    @full_name = name
    @basedir   = basedir

    @owner, @name = name.split('/')

    if args.is_a? String
      @version = SemVer.new(args)
    end
  end

  def sync(options = {})
    return if insync?

    if insync?
      #logger.debug1 "Module #{@full_name} already matches version #{@version}"
    elsif File.exist? metadata_path
      #logger.debug "Module #{@full_name} is installed but doesn't match version #{@version}, upgrading"

      # A Pulp based puppetforge http://www.pulpproject.org/ wont support
      # `puppet module install abc/xyz --version=v1.5.9` but puppetlabs forge
      # will support `puppet module install abc/xyz --version=1.5.9`
      #
      # Removing v from the semver for constructing the command ensures
      # compatibility across both
      cmd = []
      cmd << 'upgrade'
      cmd << "--version=#{@version.to_s.sub(/^v/,'')}" if @version
      cmd << "--ignore-dependencies"
      cmd << @full_name
      pmt cmd
    else
      FileUtils.mkdir @basedir unless File.directory? @basedir
      #logger.debug "Module #{@full_name} is not installed"
      cmd = []
      cmd << 'install'
      cmd << "--version=#{@version.to_s.sub(/^v/,'')}" if @version
      cmd << "--ignore-dependencies"
      cmd << @full_name
      pmt cmd
    end
  end

  # @return [SemVer, NilClass]
  def version
    if metadata
      SemVer.new(metadata['version'])
    else
      SemVer::MIN
    end
  end

  def insync?
    @version == version
  end

  def metadata
    @metadata = JSON.parse(File.read(metadata_path)) rescue nil
  end

  def metadata_path
    File.join(full_path, 'metadata.json')
  end

  private

  def pmt(args)
    cmd = "puppet module --modulepath '#{@basedir}' #{args.join(' ')}"
    log_event = "puppet module #{args.join(' ')}, modulepath: #{@basedir.inspect}"
    logger.debug1 "Execute: #{cmd}"

    status, stdout, stderr = systemu(cmd)

    logger.debug2 "[#{log_event}] STDOUT: #{stdout.chomp}" unless stdout.empty?
    logger.debug2 "[#{log_event}] STDERR: #{stderr.chomp}" unless stderr.empty?

    unless status == 0
      e = R10K::ExecutionFailure.new("#{cmd.inspect} returned with non-zero exit value #{status.inspect}")
      e.exit_code = status
      e.stdout    = stdout
      e.stderr    = stderr
      raise e
    end
    stdout
  end
end