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

require 'rubygems/version'
require 'contrast/utils/object_share'

module Contrast
  module Components
    module AppContext
      # 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 Application,
      # including the Client, Process, and Server information.
      class Interface
        include Contrast::Components::ComponentBase
        include Contrast::Components::Interface

        access_component :agent, :analysis, :config, :logging

        DEFAULT_APP_NAME    = 'rails'
        DEFAULT_APP_PATH    = '/'
        DEFAULT_SERVER_NAME = 'localhost'
        DEFAULT_SERVER_PATH = '/'

        def initialize
          original_pid
        end

        def server_type
          @_server_type ||= begin
            tmp = CONFIG.root.server.type
            tmp = Contrast::Agent.framework_manager.server_type unless Contrast::Utils::StringUtils.present?(tmp)
            tmp
          end
        end

        def name
          @_name ||= begin
            tmp = CONFIG.root.application.name
            tmp = Contrast::Agent.framework_manager.app_name unless Contrast::Utils::StringUtils.present?(tmp)
            tmp = File.basename(Dir.pwd) unless Contrast::Utils::StringUtils.present?(tmp)
            Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_NAME)
          rescue StandardError
            DEFAULT_APP_NAME
          end
        end

        def path
          @_path ||= begin
            tmp = CONFIG.root.application.path
            Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_PATH)
          rescue StandardError
            DEFAULT_APP_PATH
          end
        end

        def server_name
          @_server_name ||= begin
            tmp = CONFIG.root.server.name
            tmp = Socket.gethostname unless Contrast::Utils::StringUtils.present?(tmp)
            tmp = Contrast::Utils::StringUtils.force_utf8(tmp)
            Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_NAME)
          rescue StandardError
            DEFAULT_SERVER_NAME
          end
        end

        def server_path
          @_server_path ||= begin
            tmp = CONFIG.root.server.path
            tmp = Dir.pwd unless Contrast::Utils::StringUtils.present?(tmp)
            Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_PATH)
          rescue StandardError
            DEFAULT_SERVER_PATH
          end
        end

        def build_app_startup_message
          msg = Contrast::Api::Dtm::ApplicationCreate.new

          msg.group            = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.group
          msg.app_version      = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.version.to_s
          msg.code             = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.code
          msg.metadata         = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.metadata
          # Other fields have limits in TeamServer, the rest don't.
          msg.session_id       = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.session_id,        truncate: false
          msg.session_metadata = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.session_metadata,  truncate: false

          msg
        end

        def build_agent_startup_message
          msg = Contrast::Api::Dtm::AgentStartup.new
          msg.server_name      = Contrast::Utils::StringUtils.protobuf_format server_name
          msg.server_path      = Contrast::Utils::StringUtils.protobuf_format server_path
          msg.server_type      = Contrast::Utils::StringUtils.protobuf_format server_type
          msg.server_version   = Contrast::Agent::VERSION
          msg.version          = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.server.version
          msg.environment      = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.server.environment
          msg.server_tags      = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.server.tags
          msg.application_tags = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.tags
          msg.library_tags     = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.inventory.tags
          msg.finding_tags     = Contrast::Utils::StringUtils.protobuf_format ASSESS.tags
          logger.info('Application context',
                      server_name: msg.server_name,
                      server_path: msg.server_path,
                      server_type: msg.server_type,
                      application_name: name,
                      application_path: path,
                      application_language: Contrast::Utils::ObjectShare::RUBY)

          msg
        end

        def pid
          Process.pid
        end

        def ppid
          Process.ppid
        end

        def pgid
          Process.getpgid(pid)
        end

        def client_id
          @_client_id ||= [name, pgid].join('-')
        end

        def instrument_middleware_stack?
          !Contrast::Utils::JobServersRunning.job_servers_running?
        end

        def disabled_agent_rake_tasks
          CONFIG.root.agent.ruby.disabled_agent_rake_tasks
        end

        # Determines if the Process we're currently in matches that of the
        # Process in which the App Context instance was created.
        # If it doesn't, that indicates the running context is in a new
        # Process.
        # @return [Boolean] if we're in the original Process in which the
        #   App Context instance was initialized.
        def in_new_process?
          current_pid = Process.pid
          original_pid = pid
          current_pid != original_pid
        end

        private

        def original_pid
          @_original_pid ||= Process.pid
        end
      end

      COMPONENT_INTERFACE = Interface.new
    end
  end
end