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

require 'contrast/components/base'

module Contrast
  module Components
    module Sampling
      module Constants
        DEFAULT_SAMPLING_ENABLED            = false
        DEFAULT_SAMPLING_BASELINE           = 5
        DEFAULT_SAMPLING_REQUEST_FREQUENCY  = 5
        DEFAULT_SAMPLING_RESPONSE_FREQUENCY = 25
        DEFAULT_SAMPLING_WINDOW_MS          = 180_000
      end

      module ClassMethods # :nodoc:
        include Contrast::Components::ComponentBase
        include Constants

        def sampling_enabled?
          sampling_control[:enabled]
        end

        def sampling_control
          @_sampling_control ||= begin
            config_settings = ::Contrast::CONFIG.assess&.sampling
            settings = ::Contrast::SETTINGS&.assess_state&.sampling_settings
            {
                enabled: enabled?(config_settings, settings),
                baseline: check_baseline(config_settings, settings),
                request_frequency: check_request_frequency(config_settings, settings),
                response_frequency: check_response_frequency(config_settings, settings),
                window: check_window(config_settings, settings)
            }
          end
        end

        # Used to reset sampling when settings from TeamServer change
        def reset_sampling_control
          @_sampling_control = nil
        end

        private

        # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
        #   local user input
        # @param settings [Contrast::Agent::Reporting::Settings::Sampling, nil] the Sampling settings as provided by
        # TeamServer
        # @return [Boolean] the resolution of the config_settings, settings, and default value
        def enabled? config_settings, settings
          true?([config_settings&.enable, settings&.enabled, DEFAULT_SAMPLING_ENABLED].compact[0])
        end

        # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
        #   local user input
        # @param settings [Contrast::Agent::Reporting::Settings::Sampling] the Sampling settings as provided by
        # TeamServer
        # @return [Integer] the resolution of the config_settings, settings, and default value
        def check_baseline config_settings, settings
          [config_settings&.baseline, settings&.baseline].map(&:to_i).find(&:positive?) || DEFAULT_SAMPLING_BASELINE
        end

        # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
        #   local user input
        # @param settings [Contrast::Agent::Reporting::Settings::Sampling] the Sampling settings as provided by
        # TeamServer
        # @return [Integer] the resolution of the config_settings, settings, and default value
        def check_request_frequency config_settings, settings
          [config_settings&.request_frequency, settings&.request_frequency].map(&:to_i).find(&:positive?) ||
              DEFAULT_SAMPLING_REQUEST_FREQUENCY
        end

        # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
        #   local user input
        # @param settings [Contrast::Agent::Reporting::Settings::Sampling] the Sampling settings as provided by
        # TeamServer
        # @return [Integer] the resolution of the config_settings, settings, and default value
        def check_response_frequency config_settings, settings
          [config_settings&.response_frequency, settings&.response_frequency].map(&:to_i).find(&:positive?) ||
              DEFAULT_SAMPLING_RESPONSE_FREQUENCY
        end

        # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
        #   local user input
        # @param settings [Contrast::Agent::Reporting::Settings::Sampling] the Sampling settings as provided by
        # TeamServer
        # @return [Integer] the resolution of the config_settings, settings, and default value
        def check_window config_settings, settings
          [config_settings&.window_ms, settings&.window_ms].map(&:to_i).find(&:positive?) || DEFAULT_SAMPLING_WINDOW_MS
        end
      end

      module InstanceMethods # :nodoc:
        include Contrast::Components::ComponentBase
        include Constants
        include ClassMethods
      end

      class Interface # :nodoc:
        include InstanceMethods
        extend ClassMethods
        include Contrast::Config::BaseConfiguration

        CANON_NAME = 'assess.sampling'
        NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }"
        CONFIG_VALUES = %w[enable baseline request_frequency response_frequency window_ms].cs__freeze

        # @return [Integer, nil]
        attr_reader :baseline
        # @return [Integer, nil]
        attr_reader :request_frequency
        # @return [Integer, nil]
        attr_reader :response_frequency
        # @return [Integer, nil]
        attr_reader :window_ms
        # @return [String]
        attr_reader :canon_name

        def initialize hsh = {}
          @canon_name = CANON_NAME
          return unless hsh

          @enable = hsh[:enable]
          @baseline = hsh[:baseline]
          @request_frequency = hsh[:request_frequency]
          @response_frequency = hsh[:response_frequency]
          @window_ms = hsh[:window_ms]
        end

        # @return [Boolean, false]
        def enable
          !!@enable
        end

        # Converts current configuration to effective config values class and appends them to
        # EffectiveConfig class.
        #
        # @param effective_config [Contrast::Agent::DiagnosticsConfig::EffectiveConfig]
        def to_effective_config effective_config
          confirm_sources

          add_single_effective_value(effective_config, 'enable', sampling_control[:enabled], canon_name, NAME_PREFIX)
          add_single_effective_value(effective_config, 'baseline', sampling_control[:baseline], canon_name, NAME_PREFIX)
          add_single_effective_value(effective_config, 'window_ms', sampling_control[:window], canon_name, NAME_PREFIX)
          add_single_effective_value(effective_config,
                                     'request_frequency',
                                     sampling_control[:request_frequency],
                                     canon_name,
                                     NAME_PREFIX)
          add_single_effective_value(effective_config,
                                     'response_frequency',
                                     sampling_control[:response_frequency],
                                     canon_name,
                                     NAME_PREFIX)
        end

        private

        # Confirm the sources for the sample entries to ensure that we use 'Default' if we've fallen
        # back to the default values.
        def confirm_sources
          if sampling_control[:enabled] == DEFAULT_SAMPLING_ENABLED
            Contrast::CONFIG.sources.set('assess.sampling.enable', Contrast::Components::Config::Sources::DEFAULT)
          end
          if sampling_control[:window] == DEFAULT_SAMPLING_WINDOW_MS
            Contrast::CONFIG.sources.set('assess.sampling.window_ms', Contrast::Components::Config::Sources::DEFAULT)
          end
          {
              'baseline' => :baseline,
              'request_frequency' => :request_frequency,
              'response_frequency' => :response_frequency
          }.each do |k, v|
            if sampling_control[v] == cs__class.cs__const_get("DEFAULT_SAMPLING_#{ v.upcase }")
              Contrast::CONFIG.sources.set("assess.sampling.#{ k }", Contrast::Components::Config::Sources::DEFAULT)
            end
          end
        end
      end
    end
  end
end