require 'openssl'
require 'ipaddr'

module Fluent
  class Anonymizer

    attr_reader :log

    HASH_ALGORITHM = %w(md5 sha1 sha256 sha384 sha512 ipaddr_mask)
    DIGEST = {
      "md5" => Proc.new { OpenSSL::Digest.new('md5') },
      "sha1" => Proc.new { OpenSSL::Digest.new('sha1') },
      "sha256" => Proc.new { OpenSSL::Digest.new('sha256') },
      "sha384" => Proc.new { OpenSSL::Digest.new('sha384') },
      "sha512" => Proc.new { OpenSSL::Digest.new('sha512') }
    }

    def initialize(plugin, conf)
      @log = plugin.log
      @hash_salt = plugin.hash_salt
      @ipv4_mask_subnet = plugin.ipv4_mask_subnet
      @ipv6_mask_subnet = plugin.ipv6_mask_subnet

      @hash_keys = {}
      conf.keys.select{|k| k =~ /_keys$/}.each do |key|
        hash_algorithm_name = key.sub('_keys','')
        raise Fluent::ConfigError, "anonymizer: unsupported key #{hash_algorithm_name}" unless HASH_ALGORITHM.include?(hash_algorithm_name)
        conf[key].gsub(' ', '').split(',').each do |record_key|
          @hash_keys.store(record_key.split('.'), hash_algorithm_name)
        end
      end

      if @hash_keys.empty?
        raise Fluent::ConfigError, "anonymizer: missing hash keys setting."
      end
      log.info "anonymizer: adding anonymize rules for each field. #{@hash_keys}"

      if plugin.is_a?(Fluent::Output)
        unless have_tag_option?(plugin)
          raise Fluent::ConfigError, "anonymizer: missing remove_tag_prefix, remove_tag_suffix, add_tag_prefix or add_tag_suffix."
        end
      end
    end

    def anonymize(record)
      @hash_keys.each do |hash_key, hash_algorithm|
        record = anonymize_record(record, hash_key, hash_algorithm)
      end
      record
    end

    private

    def anonymize_record(record, key, hash_algorithm)
      if record.has_key?(key.first)
        if key.size == 1
          record[key.first] = anonymize_values(record[key.first], hash_algorithm)
        else
          record[key.first] = anonymize_record(record[key.first], key[1..-1], hash_algorithm)
        end
      end
      record
    end

    def anonymize_values(data, hash_algorithm)
      begin
        if data.is_a?(Array)
          data = data.collect { |v| anonymize_value(v, hash_algorithm, @hash_salt) }
        else
          data = anonymize_value(data, hash_algorithm, @hash_salt)
        end
      rescue => e
        log.error "anonymizer: failed to anonymize record. :message=>#{e.message} :data=>#{data}"
        log.error e.backtrace.join("\n")
      end
      data
    end

    def anonymize_value(message, algorithm, salt)
      case algorithm
      when 'md5','sha1','sha256','sha384','sha512'
        DIGEST[algorithm].call.update(salt).update(message.to_s).hexdigest
      when 'ipaddr_mask'
        address = IPAddr.new(message)
        subnet = address.ipv4? ? @ipv4_mask_subnet : @ipv6_mask_subnet
        address.mask(subnet).to_s
      else
        log.warn "anonymizer: unknown algorithm #{algorithm} has called."
      end
    end

    def have_tag_option?(plugin)
      plugin.tag ||
        plugin.remove_tag_prefix || plugin.remove_tag_suffix ||
        plugin.add_tag_prefix || plugin.add_tag_suffix
    end
  end
end