# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

require_relative "file_reader"
require "logstash/settings"

module LogStash module Modules class LogStashConfig
  include LogStash::Util::Loggable
  # We name it `modul` here because `module` has meaning in Ruby.
  def initialize(modul, settings)
    @directory = ::File.join(modul.directory, "logstash")
    @name = modul.module_name
    @settings = settings
  end

  def template
    ::File.join(@directory, "#{@name}.conf.erb")
  end

  def configured_inputs(default = [], aliases = {})
    name = "var.inputs"
    values = get_setting(LogStash::Setting::SplittableStringArray.new(name, String, default))
    aliases.each { |k, v| values << v if values.include?(k) }
    aliases.invert.each { |k, v| values << v if values.include?(k) }
    values.flatten.uniq
  end

  def alias_settings_keys!(aliases)
    aliased_settings = alias_matching_keys(aliases, @settings)
    @settings = alias_matching_keys(aliases.invert, aliased_settings)
  end

  def array_to_string(array)
    "[#{array.collect { |i| "'#{i}'" }.join(", ")}]"
  end

  def csv_string(array)
    "'#{array.join(',')}'"
  end

  def get_setting(setting_class)
    raw_value = @settings[setting_class.name]
    # If we dont check for NIL, the Settings class will try to coerce the value
    # and most of the it will fails when a NIL value is explicitly set.
    # This will be fixed once we wrap the plugins settings into a Settings class
    setting_class.set(raw_value) unless raw_value.nil?
    setting_class.value
  end

  def setting(name, default)
    # by default we use the more permissive setting which is a `NullableString`
    # This is fine because the end format of the logstash configuration is a string representation
    # of the pipeline. There is a good reason why I think we should use the settings classes, we
    # can `preprocess` a template and generate a configuration from the defined settings
    # validate the values and replace them in the template.
    case default
      when String
        get_setting(LogStash::Setting::NullableString.new(name, default.to_s))
      when Numeric
        get_setting(LogStash::Setting::Numeric.new(name, default))
      when true, false
        get_setting(LogStash::Setting::Boolean.new(name, default))
      else
        get_setting(LogStash::Setting::NullableString.new(name, default.to_s))
      end
  end

  def has_setting?(name)
    @settings.key?(name)
  end

  def raw_setting(name)
    @settings[name]
  end

  def fetch_raw_setting(name, default)
    @settings.fetch(name, default)
  end

  def elasticsearch_output_config(type_string = nil, index_suffix = "-%{+YYYY.MM.dd}")
    hosts = array_to_string(get_setting(LogStash::Setting::SplittableStringArray.new("var.elasticsearch.hosts", String, ["localhost:9200"])))
    index = "#{@name}#{index_suffix}"
    user = @settings["var.elasticsearch.username"]
    password = @settings["var.elasticsearch.password"]
    lines = ["hosts => #{hosts}", "index => \"#{index}\""]
    lines.push(user ? "user => \"#{user}\"" : nil)
    lines.push(password ? "password => \"#{password.value}\"" : nil)
    lines.push(type_string ? "document_type => #{type_string}" : nil)
    lines.push("ssl_enabled => #{@settings.fetch('var.elasticsearch.ssl.enabled', false)}")
    if cacert = @settings["var.elasticsearch.ssl.certificate_authority"]
      lines.push("ssl_certificate_authorities => \"#{cacert}\"") if cacert
    end
    # NOTE: the first line should be indented in the conf.erb
    <<-CONF
elasticsearch {
    #{lines.compact.join("\n    ")}
    manage_template => false
  }
CONF
  end

  def config_string
    # process the template and settings
    # send back as a string
    renderer = ERB.new(FileReader.read(template))
    renderer.result(binding)
  end

  private
  # For a first version we are copying the values of the original hash,
  # this might become problematic if we users changes the values of the
  # settings in the template, which could result in an inconsistent view of the original data
  #
  # For v1 of the feature I think its an OK compromise, v2 we have a more advanced hash that
  # support alias.
  def alias_matching_keys(aliases, target)
    aliased_target = target.dup

    aliases.each do |matching_key_prefix, new_key_prefix|
      target.each do |k, v|
        re = /^#{matching_key_prefix}\./

        if k =~ re
          alias_key = k.gsub(re, "#{new_key_prefix}.")

          # If the user setup the same values twices with different values lets just halt.
          raise "Cannot create an alias, the destination key has already a value set: original key: #{k}, alias key: #{alias_key}" if (!aliased_target[alias_key].nil? && aliased_target[alias_key] != v)
          aliased_target[alias_key] = v unless v.nil?
        end
      end
    end

    aliased_target
  end
end end end
