# frozen_string_literal: true

require 'set'
require 'bolt/config'
require 'bolt/inventory/group'
require 'bolt/inventory/inventory'
require 'bolt/target'
require 'bolt/util'
require 'bolt/plugin'
require 'yaml'

module Bolt
  class Inventory
    ENVIRONMENT_VAR = 'BOLT_INVENTORY'

    class ValidationError < Bolt::Error
      attr_accessor :path

      def initialize(message, offending_group)
        super(message, 'bolt.inventory/validation-error')
        @_message = message
        @path = [offending_group].compact
      end

      def details
        { 'path' => path }
      end

      def add_parent(parent_group)
        @path << parent_group
      end

      def message
        if path.empty?
          @_message
        else
          "#{@_message} for group at #{path}"
        end
      end
    end

    class WildcardError < Bolt::Error
      def initialize(target)
        super("Found 0 nodes matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
      end
    end

    def self.from_config(config, plugins)
      logger = Logging.logger[self]

      if ENV.include?(ENVIRONMENT_VAR)
        begin
          data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
          raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
          logger.debug("Loaded inventory from environment variable #{ENVIRONMENT_VAR}")
        rescue Psych::Exception
          raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
        end
      else
        data = if config.inventoryfile
                 Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
               else
                 i = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
                 logger.debug("Loaded inventory from #{config.default_inventoryfile}") if i
                 i
               end
        # This avoids rubocop complaining about identical conditionals
        logger.debug("Loaded inventory from #{config.inventoryfile}") if config.inventoryfile
      end

      # Resolve plugin references from transport config
      config.transports.each_value do |t|
        t.resolve(plugins) unless t.resolved?
      end

      inventory = create_version(data, config.transport, config.transports, plugins)
      inventory.validate
      inventory
    end

    def self.create_version(data, transport, transports, plugins)
      version = (data || {}).delete('version') { 2 }

      case version
      when 2
        Bolt::Inventory::Inventory.new(data, transport, transports, plugins)
      else
        raise ValidationError.new("Unsupported version #{version} specified in inventory", nil)
      end
    end

    def self.empty
      config  = Bolt::Config.default
      plugins = Bolt::Plugin.setup(config, nil)

      create_version({}, config.transport, config.transports, plugins)
    end
  end
end