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

require 'monitor'

module Contrast
  module Components
    module ContrastService
      # A wrapper build around the Common Agent Configuration project to allow
      # for access of the values contained in its
      # parent_configuration_spec.yaml.
      # Specifically, this allows for querying the state of the connection to
      # the Service, as well as sending a message to the Service.
      class Interface
        include Contrast::Components::ComponentBase

        DEFAULT_SERVICE_LOG = 'contrast_service.log'
        DEFAULT_SERVICE_LEVEL = :TRACE
        # The Rails ActionDispatch regexp for localhost IP + literal localhost
        # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L32
        LOCALHOST = Regexp.union([
                                   /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/,
                                   /^localhost$/
                                 ])

        def use_bundled_service?
          # Validates the config to decide if it's suitable for starting
          # the bundled service

          # Requirement says "must be true" but that
          # should be "must not be false" -- oops.
          @_use_bundled_service ||= !false?(::Contrast::CONFIG.root.agent.start_bundled_service) &&
              # Either a valid host or a valid socket
              # Path validity is the service's problem
              (LOCALHOST.match?(host) || !!socket_path)
        end

        def use_agent_communication?
          return @_use_agent_communication unless @_use_agent_communication.nil?

          @_use_agent_communication = true?(::Contrast::CONFIG.root.agent.service.bypass)
        end

        # If we're using the agent directly and not using protect, then there is no need to start the service. Because
        # we only know this at startup when hardcoded as such (b/c TS could turn protect on otherwise), we can only do
        # so when bypass is on and protect is off in local config
        #
        # @return [Boolean]
        def unnecessary?
          ::Contrast::CONTRAST_SERVICE.use_agent_communication? && ::Contrast::PROTECT.forcibly_disabled?
        end

        def host
          @_host ||=
            (::Contrast::CONFIG.root.agent.service.host || Contrast::Config::ServiceConfiguration::DEFAULT_HOST).to_s
        end

        def port
          @_port ||=
            (::Contrast::CONFIG.root.agent.service.port || Contrast::Config::ServiceConfiguration::DEFAULT_PORT).to_i
        end

        def socket_path
          @_socket_path ||= ::Contrast::CONFIG.root.agent.service.socket
        end

        def use_tcp?
          socket_path.nil?
        end

        def logger_path
          @_logger_path ||= ::Contrast::CONFIG.root.agent.service.logger.path || DEFAULT_SERVICE_LOG
        end

        def logger_level
          @_logger_level ||= ::Contrast::CONFIG.root.agent.service.logger.level || DEFAULT_SERVICE_LEVEL
        end

        private

        def disabled?
          @_disabled = false?(::Contrast::CONFIG.root.agent.start_bundled_service) if @_disabled.nil?
          @_disabled
        end
      end
    end
  end
end