# Requirements
# =====================================================================

# package
require 'qb/cli/help'


# Declarations
# =======================================================================

module QB; end


# Definitions
# =======================================================================

module QB::CLI 
  
  # Run a QB role.
  # 
  # @param [Array<String>] args
  #   CLI args to work with.
  # 
  # @return [Fixnum]
  #   Exit status code from `ansible-playbook` command, unless we invoked 
  #   help or error'd out in another way before the run (in which case `1`
  #   is returned).
  # 
  def self.run args
    role_arg = args.shift
    QB.debug "role arg", role_arg
    
    begin
      role = QB::Role.require role_arg
    rescue QB::Role::NoMatchesError => e
      puts "ERROR - #{ e.message }\n\n"
      # exits with status code 1
      return help
    rescue QB::Role::MultipleMatchesError => e
      puts "ERROR - #{ e.message }\n\n"
      return 1
    end
    
    QB.check_qb_version role
    
    options = QB::Options.new role, args
    
    QB.debug "role options set on cli", options.role_options.select {|k, o|
      !o.value.nil?
    }
    
    QB.debug "qb options", options.qb
    
    cwd = Dir.getwd
    
    dir = nil
    
    if role.uses_default_dir?
      # get the target dir
      dir = case args.length
      when 0
        # in this case, a dir has not been provided
        # 
        # in some cases (like projects) the dir can be figured out in other ways:
        # 
        
        if options.ask?
          default = begin
            role.default_dir cwd, options.role_options
          rescue QB::UserInputError => e
            NRSER::NO_ARG
          end
          
          QB::CLI.ask name: "target directory (`qb_dir`)",
                      type: t.non_empty_str,
                      default: default
          
        else
          role.default_dir cwd, options.role_options
        end
        
      when 1
        # there is a single positional arg, which is used as dir
        args[0]
        
      else
        # there are multiple positional args, which is not allowed
        raise "can't supply more than one argument: #{ args.inspect }"
        
      end
      
      QB.debug "input_dir", dir
      
      # normalize to expanded path (has no trailing slash)
      dir = File.expand_path dir
      
      QB.debug "normalized_dir", dir
      
      # create the dir if it doesn't exist (so don't have to cover this in
      # every role)
      if role.mkdir
        FileUtils.mkdir_p dir unless File.exists? dir
      end
    
      saved_options_path = Pathname.new(dir) + '.qb-options.yml'
      
      saved_options = if saved_options_path.exist?
        # convert old _ separated names to - separated
        YAML.load(saved_options_path.read).map {|role_options_key, role_options|
          [
            role_options_key,
            role_options.map {|name, value|
              [QB::Options.cli_ize_name(name), value]
            }.to_h
          ]
        }.to_h.tap {|saved_options|
          QB.debug "found saved options", saved_options
        }
      else
        QB.debug "no saved options"
        {}
      end
      
      if saved_options.key? role.options_key
        role_saved_options = saved_options[role.options_key]
        
        QB.debug "found saved options for role", role_saved_options
        
        role_saved_options.each do |option_cli_name, value|
          option = options.role_options[option_cli_name]
          
          if option.value.nil?
            QB.debug "setting from saved options", option: option, value: value
            
            option.value = value
          end
        end
      end
    end # unless default_dir == false
    
    
    # Interactive Input
    # =====================================================================
    
    if options.ask?
      # Incomplete
      raise "COMING SOON!!!...?"
      QB::CLI.ask_for_options role: role, options: options
    end
    
    
    # Validation
    # =====================================================================
    # 
    # Should have already been taken care of if we used interactive input.
    # 
    
    # check that required options are present
    missing = options.role_options.values.select {|option|
      option.required? && option.value.nil?
    }
    
    unless missing.empty?
      puts "ERROR: options #{ missing.map {|o| o.cli_name } } are required."
      return 1
    end
    
    set_options = options.role_options.select {|k, o| !o.value.nil?}
    
    QB.debug "set options", set_options
    
    playbook_role = {'role' => role.name}
    
    playbook_vars = {
      'qb_dir' => dir,
      # depreciated due to mass potential for conflict
      'dir' => dir,
      'qb_cwd' => cwd,
      'qb_user_roles_dir' => QB::USER_ROLES_DIR.to_s,
    }
    
    set_options.values.each do |option|
      playbook_role[option.var_name] = option.value
    end
    
    play =
    {
      'hosts' => options.qb['hosts'],
      'vars' => playbook_vars,
      # 'gather_subset' => ['!all'],
      'gather_facts' => options.qb['facts'],
      'pre_tasks' => [
        {
          'qb_facts' => {
            'qb_dir' => dir,
          }
        },
      ],
      'roles' => [
        'nrser.blockinfile',
        playbook_role
      ],
    }
    
    if options.qb['user']
      play['become'] = true
      play['become_user'] = options.qb['user']
    end
    
    playbook = [play]
    
    QB.debug "playbook", playbook
    
    env = QB::Ansible::Env.new
    
    # stick the role path in front to make sure we get **that** role
    env.roles_path.unshift role.path.expand_path.dirname
    
    cmd = QB::Ansible::Cmds::Playbook.new \
      env: env,
      playbook: playbook,
      role_options: options,
      chdir: (File.exists?('./ansible/ansible.cfg') ? './ansible' : nil)
    
    # print
    # =====
    # 
    # print useful stuff for debugging / running outside of qb
    # 
    
    if options.qb['print'].include? 'options'
      puts "SET OPTIONS:\n\n#{ YAML.dump set_options }\n\n"
    end
    
    if options.qb['print'].include? 'env'
      puts "ENV:\n\n#{ YAML.dump cmd.env.to_h }\n\n"
    end
    
    if options.qb['print'].include? 'cmd'
      puts "COMMAND:\n\n#{ cmd.prepare }\n\n"
    end
    
    if options.qb['print'].include? 'playbook'
      puts "PLAYBOOK:\n\n#{ YAML.dump playbook }\n\n"
    end
    
    # stop here if we're not supposed to run
    exit 0 if !options.qb['run']
    
    # run
    # ===
    # 
    # stuff below here does stuff
    # 
    
    # save the options back
    if (
      dir &&
      # we set some options that we can save
      set_options.values.select {|o| o.save? }.length > 0 &&
      # the role says to save options
      role.save_options
    )
      saved_options[role.options_key] = set_options.select{|key, option|
        option.save?
      }.map {|key, option|
        [key, option.value]
      }.to_h
      
      unless saved_options_path.dirname.exist?
        FileUtils.mkdir_p saved_options_path.dirname
      end
      
      saved_options_path.open('w') do |f|
        f.write YAML.dump(saved_options)
      end
    end
    
    status = cmd.stream
    
    if status != 0
      $stderr.puts "ERROR ansible-playbook failed."
    end
    
    # exit status
    status
  end # .run
  
end # module QB::CLI