require 'dotenv'
require 'pp'
require 'logger'
require 'tempfile'
require 'active_support'

module Envault
  class Core
    attr_accessor :logger, :cryptor, :prefix

    def initialize(config: nil, profile: nil, prefix: nil, debug: false)
      @logger = Logger.new(STDOUT)
      @logger.level = debug ? Logger::DEBUG : Logger::INFO
      profile = get_profile(config, profile)
      @cryptor = if profile[:provider] == 'kms'
        Cryptor::Kms.new(profile)
      else
        Cryptor::Simple.new(profile)
      end
      @prefix = prefix || profile[:prefix] || DEFAULT_ENV_PREFIX
    end

    def encrypt_yaml(path, keys = nil)
      hash = YAML.load_file(path)
      encrypt_process(hash, keys)
    end

    def encrypt_process(hash, keys = nil)
      cipher_keys = get_cipher_keys(hash, keys)
      encrypted = hash.map do |k, v|
        if cipher_keys.include?(k)
          encrypt_value(@prefix + k, v)
        else
          [k, v]
        end
      end
      Hash[encrypted]
    end

    def encrypt_value(key, value)
      [key, @cryptor.encrypt(value)]
    end

    def decrypt_yaml(path)
      hash = YAML.load_file(path)
      decrypt_process(hash)
    end

    def decrypt_process(hash)
      cipher_keys = get_cipher_keys(hash)
      decrypted = hash.map do |k, v|
        if cipher_keys.include?(k)
          decrypt_value(k.gsub(/^#{@prefix}/, ''), v)
        else
          [k, v]
        end
      end
      Hash[decrypted]
    end

    def decrypt_value(key, value)
      [key, @cryptor.decrypt(value)]
    end

    def get_cipher_keys(hash, keys = ["^#{@prefix}.*"])
      all_keys = hash.keys
      if keys
        regexps = []
        keys.each do |key|
          regexps << Regexp.new(key)
        end
        results = regexps.map do |regexp|
          all_keys.select do |key|
            regexp =~ key
          end
        end
        results.flatten
      else
        all_keys
      end
    end

    def load(path = DEFAULT_SOURCE_FILE)
      hash = decrypt_yaml(path)

      Tempfile.create("dotenv-vault") do |f|
        Formatter.write_escape_yaml(f.path, hash)
        Dotenv.load(f.path)
      end
    end

    private

    def get_cryptor(passphrase, sign_passphrase, salt)
      key = ActiveSupport::KeyGenerator.new(passphrase).generate_key(salt, 32)
      signature_key = ActiveSupport::KeyGenerator.new(sign_passphrase).generate_key(salt, 32) if sign_passphrase

      if signature_key
        ActiveSupport::MessageEncryptor.new(key, signature_key, cipher: DEFAULT_CIPHER, digest: DEFAULT_DIGEST)
      else
        ActiveSupport::MessageEncryptor.new(key, cipher: DEFAULT_CIPHER, digest: DEFAULT_DIGEST)
      end
    end

    def get_profile(config_path, profile_name)
      return get_profile_form_env unless config_path
      config = YAML.load_file(config_path)
      return get_profile_form_env unless config
      profile = config[profile_name]
      unless profile
        raise %Q{invalid profile [#{profile_name}].}
      end
      if profile['provider'] == 'kms'
        {
          provider: profile['provider'],
          key_id: profile['key_id'],
          prefix: profile['prefix']
        }
      else
        {
          passphrase: profile['passphrase'],
          sign_passphrase: profile['sign_passphrase'],
          salt: profile['salt'],
          prefix: profile['prefix']
        }
      end
    end

    def get_profile_form_env
      {
        passphrase: ENV['ENVAULT_PASSPHRASE'],
        sign_passphrase: ENV['ENVAULT_SIGN_PASSPHRASE'],
        salt: ENV['ENVAULT_SALT'],
        prefix: ENV['ENVAULT_PREFIX']
      }
    end
  end
end