# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'contrast/utils/boolean_util'
require 'contrast/utils/env_configuration_item'
require 'contrast/utils/object_share'
require 'contrast/configuration'

module Contrast
  module Components
    # This component encapsulates reference to the configuration file.
    # At the time of writing, the configuration file is a yaml file reflecting
    # the 'common agent configuration' specification.
    # 'Config' and 'configuration' are to be interpreted as referring
    # specifically to these files and specifications, they are not generic
    # terms for other avenues of user configuration.
    #
    # This component is responsible for...
    #  - encapsulating file access & concomitant error conditions
    #  - implementing validity checks with respect to the specification
    #  - memoizing/streamline field accesses
    #
    # Config fails fast.  if it's not valid, the agent should break, and
    # it should break LOUDLY.  Better to waste half an hour of the sysadmin's
    # time than to silently fail to deliver functionality.
    module Config
      CONTRAST_ENV_MARKER = 'CONTRAST__'

      class Interface # :nodoc:
        def initialize
          build
        end

        def build log: true
          @_valid = nil
          @config = Contrast::Configuration.new
          env_overrides
          validate(log: log)
        end
        alias_method :rebuild, :build

        # Prefer abstraction, but use #raw if you need.
        # grep 'CONFIG.raw' for opportunities to refactor.
        def raw
          @config
        end

        def root
          raw.root
        end

        def enabled?
          @_enabled = !Contrast::Utils::BooleanUtil.false?(raw.enable) if @_enabled.nil?
          @_enabled
        end

        def disabled?
          !enabled?
        end

        def protect?
          @_protect = Contrast::Utils::BooleanUtil.true?(raw.protect.enable) if @_protect.nil?
          @_protect
        end

        def assess?
          @_assess = Contrast::Utils::BooleanUtil.true?(raw.assess.enable) if @_assess.nil?
          @_assess
        end

        def session_id
          @_session_id ||= raw.application.session_id
        end

        def session_metadata
          @_session_metadata ||= raw.application.session_metadata
        end

        def valid?
          @_valid = validate(log: false) if @_valid.nil?
        end

        def invalid?
          !valid?
        end

        private

        SESSION_VARIABLES = "Invalid configuration. Setting both application.session_id and application.session_metadata is not allowed.\n"
        def validate log: false
          # The config has information about how to construct the logger.
          # If the config is invalid, and you want to know about it, then
          # you have a circular dependency if you try to log it,
          # hence `log: false`.
          if !session_id.empty? && !session_metadata.empty?
            if log
              cs__class.log_error(SESSION_VARIABLES)
            else
              puts SESSION_VARIABLES
            end
            return false
          end

          true
        end

        def env_overrides
          # For env variables resembling CONTRAST__WHATEVER__NESTED_VALUE
          # override raw.whatever.nested_value
          ENV.each do |env_key, env_value|
            next unless env_key.to_s.start_with?(CONTRAST_ENV_MARKER)

            config_item = Contrast::Utils::EnvConfigurationItem.new(env_key, env_value)
            raw.assign_value_to_path_array(config_item.dot_path_array, config_item.value)
          end
        end
      end

      COMPONENT_INTERFACE = Interface.new
    end
  end
end