# frozen_string_literal: true

require 'anyway/ext/class'
require 'anyway/ext/deep_dup'
require 'anyway/ext/hash'

module Anyway # :nodoc:
  using Anyway::Ext::Class
  using Anyway::Ext::DeepDup
  using Anyway::Ext::Hash

  # Base config class
  # Provides `attr_config` method to describe
  # configuration parameters and set defaults
  class Config
    class << self
      attr_reader :defaults, :config_attributes

      def attr_config(*args, **hargs)
        @defaults = hargs.deep_dup
        defaults.stringify_keys!
        @config_attributes = args + defaults.keys
        attr_accessor(*@config_attributes)
      end

      def config_name(val = nil)
        return (@config_name = val.to_s) unless val.nil?
        @config_name = underscore_name unless defined?(@config_name)
        @config_name
      end

      # Load config as Hash by any name
      #
      # Example:
      #
      #   my_config = Anyway::Config.for(:my_app)
      #   # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
      def for(name)
        new(name, false).load_from_sources
      end
    end

    attr_reader :config_name

    def initialize(config_name = nil, do_load = true)
      @config_name = config_name || self.class.config_name
      load if do_load
    end

    def reload
      clear
      load
      self
    end

    def clear
      self.class.config_attributes.each do |attr|
        send("#{attr}=", nil)
      end
      self
    end

    def load
      config = load_from_sources((self.class.defaults || {}).deep_dup)
      config.each do |key, val|
        set_value(key, val)
      end
    end

    def load_from_sources(config = {})
      # Handle anonymous configs
      return config unless config_name
      load_from_file(config)
      load_from_env(config)
    end

    def load_from_file(config)
      config_path = Anyway.env.fetch(config_name).delete('conf') ||
                    "./config/#{config_name}.yml"
      if config_path && File.file?(config_path)
        config.deep_merge!(parse_yml(config_path) || {})
      end
      config
    end

    def load_from_env(config)
      config.deep_merge!(Anyway.env.fetch(config_name))
      config
    end

    private

    def set_value(key, val)
      send("#{key}=", val) if respond_to?(key)
    end

    def parse_yml(path)
      require 'yaml'
      if defined?(ERB)
        YAML.safe_load(ERB.new(File.read(path)).result)
      else
        YAML.load_file(path)
      end
    end
  end
end