#!/usr/bin/env ruby
# frozen_string_literal: true

require 'thor'

module Facter
  class Cli < Thor
    class_option :color,
                 type: :boolean,
                 desc: 'Enable color output.'

    class_option :no_color,
                 type: :boolean,
                 desc: 'Disable color output.'

    class_option :config,
                 aliases: '-c',
                 type: :string,
                 desc: 'The location of the config file.'

    class_option :custom_dir,
                 type: :string,
                 repeatable: true,
                 desc: 'A directory to use for custom facts.'

    class_option :debug,
                 aliases: '-d',
                 type: :boolean,
                 desc: 'Enable debug output.'

    class_option :external_dir,
                 type: :string,
                 repeatable: true,
                 desc: 'A directory to use for external facts.'

    class_option :hocon,
                 type: :boolean,
                 desc: 'Output in Hocon format.'

    class_option :json,
                 aliases: '-j',
                 type: :boolean,
                 desc: 'Output in JSON format.'

    class_option :log_level,
                 aliases: '-l',
                 type: :string,
                 desc: 'Set logging level. Supported levels are: none, trace, debug, info, warn, error, and fatal.'

    class_option :no_block,
                 type: :boolean,
                 desc: 'Disable fact blocking.'

    class_option :no_cache,
                 type: :boolean,
                 desc: 'Disable loading and refreshing facts from the cache'

    class_option :no_custom_facts,
                 type: :boolean,
                 desc: 'Disable custom facts.'

    class_option :no_external_facts,
                 type: :boolean,
                 desc: 'Disable external facts.'

    class_option :no_ruby,
                 type: :boolean,
                 desc: 'Disable loading Ruby, facts requiring Ruby, and custom facts.'

    class_option :trace,
                 type: :boolean,
                 desc: 'Enable backtraces for custom facts.'

    class_option :verbose,
                 type: :boolean,
                 desc: 'Enable verbose (info) output.'

    class_option :show_legacy,
                 type: :boolean,
                 desc: 'Show legacy facts when querying all facts.'

    class_option :yaml,
                 aliases: '-y',
                 type: :boolean,
                 desc: 'Output in YAML format.'

    class_option :strict,
                 type: :boolean,
                 desc: 'Enable more aggressive error reporting.'

    class_option :timing,
                 type: :boolean,
                 aliases: '-t',
                 desc: 'Show how much time it took to resolve each fact'

    class_option :sequential,
                 type: :boolean,
                 desc: 'Resolve facts sequentially'

    class_option :http_debug,
                 type: :boolean,
                 desc: 'Whether to write HTTP request and responses to stderr. This should never be used in production.'

    class_option :puppet,
                 type: :boolean,
                 aliases: '-p',
                 desc: 'Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts.'

    desc '--man', 'Display manual.', hide: true
    map ['--man'] => :man
    def man(*args)
      require 'erb'
      negate_options = %w[block cache custom_facts external_facts]

      template = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'man.erb')
      erb = ERB.new(File.read(template), nil, '-')
      erb.filename = template
      puts erb.result(binding)
    end

    desc 'query', 'Default method', hide: true
    def query(*args)
      Facter.puppet_facts if options[:puppet]
      output, status = Facter.to_user_output(@options, *args)
      puts output

      status = 1 if Facter::Log.errors?
      exit status
    end

    desc 'arg_parser', 'Parse arguments', hide: true
    def arg_parser(*args)
      # ignore unknown options
      args.reject! { |arg| arg.start_with?('-') }

      Facter.values(@options, args)
    end

    desc '--version, -v', 'Print the version'
    map ['--version', '-v'] => :version
    def version(*_args)
      puts Facter::VERSION
    end

    desc '--list-block-groups', 'List block groups'
    map ['--list-block-groups'] => :list_block_groups
    def list_block_groups
      options = @options.map { |(k, v)| [k.to_sym, v] }.to_h
      Facter::Options.init_from_cli(options)

      block_groups = Facter::FactGroups.new.groups.to_yaml.lines[1..-1].join
      block_groups.gsub!(/:\s*\n/, "\n")

      puts block_groups
    end

    desc '--list-cache-groups', 'List cache groups'
    map ['--list-cache-groups'] => :list_cache_groups
    def list_cache_groups
      options = @options.map { |(k, v)| [k.to_sym, v] }.to_h
      Facter::Options.init_from_cli(options)

      cache_groups = Facter::FactGroups.new.groups.to_yaml.lines[1..-1].join
      cache_groups.gsub!(/:\s*\n/, "\n")

      puts cache_groups
    end

    desc '--help, -h', 'Help for all arguments'
    def help(*args)
      help_string = +''
      help_string << help_header(args)
      help_string << add_class_options_to_help
      help_string << add_commands_to_help

      puts help_string
    end

    no_commands do
      def help_header(_args)
        path = File.join(File.dirname(__FILE__), '../../')

        Facter::Util::FileHelper.safe_read("#{path}fixtures/facter_help_header")
      end

      def add_class_options_to_help
        help_class_options = +''
        class_options = Cli.class_options
        class_options.each do |class_option|
          option = class_option[1]
          next if option.hide

          help_class_options << build_option(option.name, option.aliases, option.description)
        end

        help_class_options
      end

      def add_commands_to_help
        help_command_options = +''
        Cli.commands.values
           .select { |command_class| command_class.instance_of?(Thor::Command) }
           .each do |command|
          help_command_options << build_option(
            command['name'],
            [command['usage'].split(',')[1]],
            command['description']
          )
        end

        help_command_options
      end

      def build_option(name, aliases, description)
        name = name.tr('_', '-')
        help_option = +''
        help_option << aliases.join(',').rjust(10)
        help_option << ' '
        help_option << "[--#{name}]".ljust(30)
        help_option << " #{description}"
        help_option << "\n"

        help_option
      end
    end

    def self.exit_on_failure?
      true
    end

    default_task :query
  end
end
