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

module Contrast
  module Components
    # This component encapsulates the statefulness of settings.
    # When we say 'settings', we're referring specifically to external
    # directives (likely provided by TeamServer) about product operation.
    # 'Settings' is not a generic term for 'configurable stuff'.
    module Settings
      # This is a class.
      class Interface
        include Contrast::Components::ComponentBase
        include Contrast::Components::Interface
        access_component :config

        attr_reader :assess_rules,
                    :protect_rules

        # Other stateful information that doesn't yet cleanly fit anywhere:

        # tainted_columns are database columns that receive unsanitized input.
        # this statefulness
        attr_reader :tainted_columns # This can probably go into assess_state?

        # These three 'state' variables represent atomic config/setting state,
        # outside of things like rule defs.

        def assess_state
          @assess_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
              enabled: false,
              sampling_features: nil
          }
        end

        def protect_state
          @protect_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
              enabled: false
          }
        end

        def application_state
          @application_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
              modes_by_id: Hash.new(:NO_ACTION),
              exclusion_matchers: [],
              disabled_assess_rules: []
          }
        end

        # These are settings that we receive & store.
        # Rules are settings too, but they're more involved.
        # So, between this block and rules, that's setting state.
        PROTECT_STATE_ATTRS       = %i[].cs__freeze
        ASSESS_STATE_ATTRS        = %i[sampling_features].cs__freeze
        APPLICATION_STATE_ATTRS   = %i[modes_by_id exclusion_matchers disabled_assess_rules].cs__freeze

        # Meta-define an accessor for each state attribute.

        PROTECT_STATE_ATTRS.each do |attr|
          define_method(attr) do
            protect_state[attr]
          end
        end

        ASSESS_STATE_ATTRS.each do |attr|
          define_method(attr) do
            assess_state[attr]
          end
        end

        APPLICATION_STATE_ATTRS.each do |attr|
          define_method(attr) do
            application_state[attr]
          end
        end

        def initialize
          reset_state
        end

        def protect_enabled?
          @_protect_enabled = !!protect_state[:enabled] if @_protect_enabled.nil?
          @_protect_enabled
        end

        def assess_enabled?
          @_assess_enabled = !!assess_state[:enabled] if @_assess_enabled.nil?
          @_assess_enabled
        end

        def code_exclusions
          exclusion_matchers.select(&:code?)
        end

        def update_from_server_features server_features
          # protect

          @_protect_enabled = nil
          protect_state[:enabled] = server_features.protect_enabled?

          # assess

          @_assess_enabled = nil
          assess_state[:enabled] = server_features.assess_enabled?
          assess_state[:sampling_settings] = server_features.assess.sampling
          Contrast::Utils::Assess::SamplingUtil.instance.update
        end

        def update_from_application_settings application_settings
          application_state.merge!(application_settings.application_state_translation)
        end

        # Wipe state to zero.
        def reset_state
          @assess_rules = {}
          @protect_rules = {}

          @tainted_columns = {}

          @assess_state = nil
          @protect_state = nil
          @application_state = nil
        end

        def build_assess_rules
          @assess_rules = {}

          Contrast::Agent::Assess::Rule::Redos.new
        end

        def build_protect_rules
          @protect_rules = {}

          # rules
          Contrast::Agent::Protect::Rule::CmdInjection.new
          Contrast::Agent::Protect::Rule::Deserialization.new
          Contrast::Agent::Protect::Rule::HttpMethodTampering.new
          Contrast::Agent::Protect::Rule::NoSqli.new
          Contrast::Agent::Protect::Rule::PathTraversal.new
          Contrast::Agent::Protect::Rule::Sqli.new
          Contrast::Agent::Protect::Rule::UnsafeFileUpload.new
          Contrast::Agent::Protect::Rule::Xss.new
          Contrast::Agent::Protect::Rule::Xxe.new
        end
      end

      COMPONENT_INTERFACE = Interface.new
    end
  end
end