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

require 'contrast/utils/object_share'
require 'contrast/config/diagnostics/effective_config_value'
require 'contrast/config/diagnostics/singleton_tools'
require 'contrast/utils/duck_utils'

module Contrast
  module Config
    module Diagnostics
      # Diagnostics tools to be included in config components.
      module Tools
        CHECK = 'd'

        extend Contrast::Config::Diagnostics::SingletonTools

        # TODO: RUBY-2113 deprecate name_prefix
        #
        # Converts current configuration from array of values to effective config values class and appends them to
        # EffectiveConfig class. Must be used inside Config Components only.
        #
        # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
        # @param config_values [] array of the names of values.
        # @param canonical_prefix [String] starting of the path to config => api.proxy...
        # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
        def add_effective_config_values(effective_config,
                                        config_values,
                                        canonical_prefix,
                                        name_prefix = canonical_prefix)
          return if config_values.to_s.empty?

          config_values.each do |config_value_name|
            Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
              config_value = send(config_value_name.to_sym)
              fill_effective_value(new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix)
              effective_config.values << new_effective_value
            rescue StandardError => e
              log_error(e)
              next
            end
          end
        end

        # TODO: RUBY-2113 deprecate name_prefix
        #
        # Converts current configuration from single value to effective config values class and appends them to
        # EffectiveConfig class. Must be used inside Config Components only.
        #
        # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
        # @param config_name [String] name of the config.
        # @param config_value [String, Boolean] value of the config.
        # @param canonical_prefix [String] starting of the path to config => api.proxy...
        # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
        def add_single_effective_value(effective_config,
                                       config_name,
                                       config_value,
                                       canonical_prefix,
                                       name_prefix = canonical_prefix)
          Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
            fill_effective_value(new_effective_value, config_value, config_name, canonical_prefix, name_prefix)
            effective_config.values << new_effective_value
          rescue StandardError => e
            log_error(e)
            next
          end
        end

        private

        # Fills instance of effective configuration value from read configuration.
        #
        # @param new_effective_value [Contrast::Config::Diagnostics::EffectiveConfigValue]
        # @param config_value [String, Boolean] value of the config
        # @param config_value_name [string] name of the value e.g. enabled?
        # @param canonical_prefix [String] starting of the path to config => api.proxy...
        # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
        # @return filled_new_effective_config [Contrast::Config::Diagnostics::EffectiveConfigValue]
        def fill_effective_value new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix
          find_source(new_effective_value, canonical_prefix, assign_name(config_value_name), name_prefix)
          if Contrast::Config::Diagnostics::SingletonTools::API_CREDENTIALS.include?(config_value_name.to_s)
            new_effective_value.value = Contrast::Configuration::EFFECTIVE_REDACTED
            return new_effective_value
          end
          new_effective_value.value = config_value.cs__is_a?(Array) ? config_value.join(',') : config_value.to_s
          new_effective_value
        end

        # Assigns a proper name for the config removing '?' out of method names.
        #
        # @param config [String] name of the configuration
        # @return [String]
        def assign_name config
          return Contrast::Utils::ObjectShare::EMPTY_STRING unless config

          name = config.dup
          if name.end_with?(Contrast::Utils::ObjectShare::QUESTION_MARK)
            # check and remove '?' : start_bundled_service? => start_bundled_service
            name.delete!(Contrast::Utils::ObjectShare::QUESTION_MARK)
            # converts name e.g. enabled => enable
            name.chop! if name.end_with?(CHECK)
            name
          end
          name
        end

        # Retrieves the config value from sources by canonical name and sets the key value.
        #
        # @param new_effective_value [Contrast::Config::Diagnostics::EffectiveConfigValue]
        # @param canonical_prefix [String] starting of the path to config => api.proxy...
        # @param config_name [String] name of the config.
        # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
        # @return [Contrast::Config::Diagnostics::EffectiveConfigValue]
        def find_source new_effective_value, canonical_prefix, config_name, name_prefix
          new_effective_value.key = "#{ name_prefix }.#{ config_name }"
          new_effective_value.canonical_name = "#{ canonical_prefix }.#{ config_name }"
          # For files we keep the whole path as source.
          source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
          new_effective_value.assign_filename(source)
          new_source = if Contrast::CONFIG.sources.configuration_file_source?(new_effective_value.canonical_name)
                         Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
                       end
          new_effective_value.source = new_source || source
          new_effective_value
        end

        # Logs any caught error.
        #
        # @param error [StandardError]
        def log_error error
          Contrast::CONFIG.proto_logger.warn(Contrast::Config::Diagnostics::Monitor::ERROR_MESSAGE,
                                             error: error, backtrace: error.backtrace)
        end
      end
    end
  end
end