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

require 'contrast/utils/timer'
require 'contrast/agent/request/request'
require 'contrast/agent/response/response'
require 'contrast/agent/inventory/database_config'
require 'contrast/components/logger'
require 'contrast/components/scope'
require 'contrast/utils/request_utils'
require 'contrast/agent/request/request_context_extend'
require 'contrast/agent/reporting/reporting_events/observed_route'
require 'contrast/agent/reporting/input_analysis/input_analysis'
require 'contrast/agent/reporting/reporting_events/application_activity'

module Contrast
  module Agent
    # This class acts to encapsulate information about the currently executed request, making it available to the Agent
    # for the duration of the request in a standardized and normalized format which the Agent understands.
    class RequestContext
      include Contrast::Components::Logger::InstanceMethods
      include Contrast::Components::Scope::InstanceMethods
      include Contrast::Utils::RequestUtils
      include Contrast::Agent::RequestContextExtend

      INPUT_ANALYSIS = Contrast::Agent::Reporting::InputAnalysis.new

      # @return [Contrast::Agent::Reporting:ApplicationActivity] the application activity found in this request
      attr_reader :activity
      # @return [Hash] context used to log the request
      attr_reader :logging_hash
      # @return [Contrast::Agent::Reporting::ObservedRoute] the route, used for coverage, of this request
      attr_reader :observed_route
      # @return [Contrast::Agent::Request] our wrapper around the Rack::Request for this context
      attr_reader :request
      # @return [Contrast::Agent::Response] our wrapper around the Rack::Response or Array for this context,
      #   only available after the application has finished its processing
      attr_reader :response
      # @return [Contrast::Agent::Reporting::RouteDiscovery] the route, used for findings, of this request
      attr_reader :discovered_route
      # @return [Contrast::Agent::Reporting::InputAnalysis]
      attr_reader :agent_input_analysis
      # @return [Array<String>] the hash of findings already reported fro this request
      attr_reader :reported_findings
      # @return [Contrast::Utils::Timer] when the context was created
      attr_reader :timer

      attr_accessor :propagation_event_count, :source_event_count

      def initialize rack_request, app_loaded: true
        with_contrast_scope do
          # all requests get a timer and hash
          @timer = Contrast::Utils::Timer.new
          @logging_hash = { request_id: __id__ }

          # instantiate helper for request and response
          @request = Contrast::Agent::Request.new(rack_request) if rack_request
          @activity = Contrast::Agent::Reporting::ApplicationActivity.new

          # build analyzer
          @do_not_track = false
          @agent_input_analysis = INPUT_ANALYSIS
          agent_input_analysis.request = request

          # flag to indicate whether the app is fully loaded
          @app_loaded = !!app_loaded

          # generic holder for properties that can be set throughout this request
          @_properties = {}

          # count of propagation events
          @propagation_event_count = 0

          # count of source events
          @source_event_count = 0

          if ::Contrast::ASSESS.enabled?
            @sample_req, @sample_res = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request)
          end

          @reported_findings = []

          handle_routes
        end
      end

      def app_loaded?
        @app_loaded
      end

      def analyze_request?
        analyze_request_assess? || analyze_req_res_protect?
      end

      def analyze_response?
        analyze_response_assess? || analyze_req_res_protect?
      end

      def analyze_req_res_protect?
        ::Contrast::PROTECT.enabled?
      end

      def analyze_request_assess?
        return false unless analyze_req_res_assess?

        @sample_req
      end

      def analyze_response_assess?
        return false unless analyze_req_res_assess?

        @sample_res &&= ::Contrast::ASSESS.scan_response?
      end

      def analyze_req_res_assess?
        ::Contrast::ASSESS.enabled?
      end

      def add_property key, value
        @_properties[key] = value
      end

      def get_property key
        @_properties[key]
      end

      def reset_activity
        @activity = Contrast::Agent::Reporting::ApplicationActivity.new
        @observed_route = Contrast::Agent::Reporting::ObservedRoute.new
      end

      private

      def handle_routes
        @observed_route = Contrast::Agent::Reporting::ObservedRoute.new
        reporting_route = Contrast::Agent.framework_manager.get_route_information(@request)
        append_to_observed_route(reporting_route)
      rescue StandardError => e
        logger.error('Unable to determine current route', e)
      end
    end
  end
end