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

require 'contrast/utils/metrics_hash'
require 'contrast/agent/telemetry/events/metric_event'
require 'contrast/agent/version'
require 'contrast/utils/os'

module Contrast
  module Agent
    module Telemetry
      # This class will hold the Startup Metrics Telemetry Event
      # The class will include initialization of the agent version, language version
      # os type, arch and version
      # application framework and version and server framework
      # It will be initialized and send in Middleware#agent_startup_routine
      class StartupMetricsEvent < Contrast::Agent::Telemetry::MetricEvent
        include Contrast::Utils::OS

        APP_AND_SERVER_DATA = ::Contrast::APP_CONTEXT.app_and_server_information.cs__freeze
        # Multi-tenant Production Environments
        SAAS_DEFAULT = { addr: 'app.contrastsecurity.com', type: 'SAAS_DEFAULT' }.cs__freeze
        SAAS_CS = { addr: /cs[[:digit:]]+\.contrastsecurity\.com/, type: 'SAAS_DEFAULT' }.cs__freeze
        SAAS_JP = { addr: 'app.contrastsecurity.jp', type: 'SAAS_DEFAULT' }.cs__freeze
        SAAS_CE = { addr: 'ce.contrastsecurity.com', type: 'SAAS_CE' }.cs__freeze
        # Multi-tenant Demo Environments
        SAAS_DEMO = { addr: 'apptwo.contrastsecurity.com', type: 'SAAS_DEMO' }.cs__freeze
        SAAS_POV = { addr: 'eval.contrastsecurity.com', type: 'SAAS_POV' }.cs__freeze
        # Multi-tenant Testing Environment
        SAAS_RESEARCH = { addr: 'security-research.contrastsecurity.com', type: 'SAAS_RESEARCH' }.cs__freeze
        SAAS_ALPHA = { addr: 'alpha.contrastsecurity.com', type: 'SAAS_ALPHA' }.cs__freeze
        SAAS_STAGING = { addr: 'teamserver-staging.contsec.com', type: 'SAAS_TESTING' }.cs__freeze
        SAAS_STAGING_TOKYO = { addr: 'teamserver-staging.contsec.jp', type: 'SAAS_TESTING' }.cs__freeze
        SAAS_TESTING = { addr: 'teamserver-darpa.contsec.com', type: 'SAAS_TESTING' }.cs__freeze
        SAAS_OPS_TESTING = { addr: 'teamserver-ops.contsec.com', type: 'SAAS_TESTING' }.cs__freeze
        # Fallback for Single-tenant Production Environments
        SAAS_CUSTOM = { addr: 'contrastsecurity.com', type: 'SAAS_CUSTOM' }.cs__freeze
        SAAS_CUSTOM_JP = { addr: 'contrastsecurity.jp', type: 'SAAS_CUSTOM' }.cs__freeze

        SINGLE_MAP_TENANTS = [
          SAAS_DEFAULT, SAAS_JP, SAAS_CE, SAAS_DEMO, SAAS_POV, SAAS_RESEARCH, SAAS_ALPHA,
          SAAS_STAGING, SAAS_STAGING_TOKYO, SAAS_TESTING, SAAS_OPS_TESTING
        ].cs__freeze
        REGEXP_MAP_TENANTS = [SAAS_CS].cs__freeze
        FALLBACK_TENANTS = [SAAS_CUSTOM, SAAS_CUSTOM_JP].cs__freeze
        # Fallback for Custom, most likely self-hosted, Environments
        EOP = 'EOP'
        REJECTED_VALUES = [nil, 'NEEDS_TO_BE_SET', Contrast::Utils::ObjectShare::EMPTY_STRING].cs__freeze

        def initialize
          super
          @settings = []
          add_config_keys ::Contrast::CONFIG.root, 'root'
          @settings << ENV.keys.select { |v| v.starts_with?('CONTRAST') }
          @settings.flatten
          add_tags
        end

        def path
          '/startup'
        end

        def add_tags
          add_system_tags
          @tags['app_framework_and_version'] = APP_AND_SERVER_DATA[:application_info].to_s
          @tags['server_framework_and_version'] = APP_AND_SERVER_DATA[:server_info].to_s
          @tags['ASSESS'] = Contrast::ASSESS.enabled?.to_s
          @tags['PROTECT'] = Contrast::PROTECT.enabled?.to_s
          @tags['settings'] = @settings.join(',')
        end

        def add_config_keys config, nested_key
          config.to_hash.reject! { |_k, v| REJECTED_VALUES.include?(v) }

          config.to_hash.each do |k, v|
            unless v.cs__class <= Contrast::Config::BaseConfiguration
              @settings << "#{ nested_key }.#{ k }"
              next
            end

            add_config_keys v, "#{ nested_key }.#{ k }"
          end
        end

        def sys_info
          @sys_info ||= get_system_information if @sys_info.nil?
          @sys_info
        end

        private

        # Here we extract the TeamServer url type
        #
        # @return [String] the type of TeamServer environment to which we're connecting
        def teamserver_type
          @_teamserver_type ||= begin
            url = Contrast::API.api_url
            if (single = SINGLE_MAP_TENANTS.find { |tenant| url.include?(tenant[:addr]) })
              single[:type]
            elsif (regexp = REGEXP_MAP_TENANTS.find { |tenant| tenant[:addr].match?(url) })
              regexp[:type]
            elsif (fallback = FALLBACK_TENANTS.find { |tenant| url.include?(tenant[:addr]) })
              fallback[:type]
            else
              EOP
            end
          end
        end

        # Here we attach the key-value pairs of the system
        #
        def add_system_tags
          @tags['teamserver'] = teamserver_type
          @tags['agent_version'] = VERSION
          @tags['ruby_version'] = RUBY_VERSION
          @tags['os_type'] = sys_info['os_type'] == 'Darwin' ? 'MacOS' : 'Linux'
          @tags['os_arch'] = sys_info['os_arch']
          @tags['os_version'] = sys_info['os_version']
        end
      end
    end
  end
end