require 'puppet/indirector/active_record'

class Puppet::Resource::ActiveRecord < Puppet::Indirector::ActiveRecord
  def search(request)
    type   = request_to_type_name(request)
    host   = request.options[:host]
    filter = request.options[:filter]

    if filter and filter[1] =~ /^(and|or)$/i then
      raise Puppet::Error, "Complex search on StoreConfigs resources is not supported"
    end

    query = build_active_record_query(type, host, filter)
    Puppet::Rails::Resource.find(:all, query)
  end

  private
  def request_to_type_name(request)
    name = request.key.split('/', 2)[0]
    type = Puppet::Type.type(name) or raise Puppet::Error, "Could not find type #{name}"
    type.name
  end

  def filter_to_active_record(filter)
    # Don't call me if you don't have a filter, please.
    filter.is_a?(Array) or raise ArgumentError, "active record filters must be arrays"
    a, op, b = filter

    case op
    when /^(and|or)$/i then
      extra = []
      first, args = filter_to_active_record a
      extra += args

      second, args = filter_to_active_record b
      extra += args

      return "(#{first}) #{op.upcase} (#{second})", extra

    when "==", "!=" then
      op = '=' if op == '=='    # SQL, yayz!
      case a
      when "title" then
        return "title #{op} ?", [b]

      when "tag" then
        return "puppet_tags.name #{op} ?", [b]

      else
        return "param_names.name = ? AND param_values.value #{op} ?", [a, b]
      end

    else
      raise ArgumentError, "unknown operator #{op.inspect} in #{filter.inspect}"
    end
  end

  def build_active_record_query(type, host, filter)
    raise Puppet::DevError, "Cannot collect resources for a nil host" unless host

    search = "(exported=? AND restype=?)"
    # Some versions of ActiveRecord just to_s a symbol, which our type is, but
    # others preserve the symbol-nature, which causes our storage (string) and
    # query (symbol) to mismatch.  So, manually stringify. --daniel 2011-09-08
    arguments = [true, type.to_s]

    if filter then
      sql, values = filter_to_active_record(filter)
      search    += " AND #{sql}"
      arguments += values
    end

    # note: we're not eagerly including any relations here because it can
    # create large numbers of objects that we will just throw out later.  We
    # used to eagerly include param_names/values but the way the search filter
    # is built ruined those efforts and we were eagerly loading only the
    # searched parameter and not the other ones.
    query = {}
    case search
    when /puppet_tags/
      query = { :joins => { :resource_tags => :puppet_tag } }
    when /param_name/
      query = { :joins => { :param_values => :param_name } }
    end

    # We're going to collect objects from rails, but we don't want any
    # objects from this host.
    if host = Puppet::Rails::Host.find_by_name(host)
      search += " AND (host_id != ?)"
      arguments << host.id
    end

    query[:conditions] = [search, *arguments]

    query
  end
end
