# Copyright (c) 2022 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'
require 'contrast/agent/response'
require 'contrast/agent/inventory/database_config'
require 'contrast/components/logger'
require 'contrast/components/scope'
require 'contrast/utils/request_utils'
require 'contrast/agent/request_context_extend'
require 'contrast/agent/reporting/reporting_events/observed_route'
require 'contrast/agent/reporting/input_analysis/input_analysis'

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.
    #
    # @attr_reader timer [Contrast::Utils::Timer] when the context was created
    # @attr_reader logging_hash [Hash] context used to log the request
    # @attr_reader speedracer_input_analysis [Contrast::Api::Settings::InputAnalysis] the protect input analysis of
    #   sources on this request
    # @attr_reader request [Contrast::Agent::Request] our wrapper around the Rack::Request for this context
    # @attr_reader response [Contrast::Agent::Response] our wrapper aroudn the Rack::Response or Array for this context,
    #   only available after the application has finished its processing
    # @attr_reader activity [Contrast::Api::Dtm::Activity] the application activity found in this request
    # @attr_reader server_activity [Contrast::Api::Dtm::ServerActivity] the server activity found in this request
    # @attr_reader route [Contrast::Api::Dtm::RouteCoverage] the route, used for findings, of this request
    # @attr_reader observed_route [Contrast::Api::Dtm::ObservedRoute] the route, used for coverage, of this request
    # @attr_reader new_observed_route [Contrast::Agent::Reporting::ObservedRoute] the route, used for coverage, of this
    # request
    class RequestContext
      include Contrast::Components::Logger::InstanceMethods
      include Contrast::Components::Scope::InstanceMethods
      include Contrast::Utils::RequestUtils
      include Contrast::Agent::RequestContextExtend

      EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new
      INPUT_ANALYSIS = Contrast::Agent::Reporting::InputAnalysis.new

      attr_reader :activity, :logging_hash, :observed_route, :new_observed_route, :request, :response, :route,
                  :speedracer_input_analysis, :agent_input_analysis, :server_activity, :timer

      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)

          @activity = Contrast::Api::Dtm::Activity.new
          @activity.http_request = request.dtm

          @server_activity = Contrast::Api::Dtm::ServerActivity.new

          # build analyzer
          @do_not_track = false
          @speedracer_input_analysis = EMPTY_INPUT_ANALYSIS_PB
          speedracer_input_analysis.request = request

          @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 = {}

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

          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::Api::Dtm::Activity.new(http_request: request.dtm)
        @server_activity = Contrast::Api::Dtm::ServerActivity.new
        @observed_route = Contrast::Api::Dtm::ObservedRoute.new # TODO: RUBY-1438 -- remove
        @new_observed_route = Contrast::Agent::Reporting::ObservedRoute.new
      end

      private

      def handle_routes
        @observed_route = Contrast::Api::Dtm::ObservedRoute.new # TODO: RUBY-1438 -- remove
        @new_observed_route = Contrast::Agent::Reporting::ObservedRoute.new
        route_dtm = Contrast::Agent.framework_manager.get_route_dtm(@request)
        new_route_coverage_dtm = Contrast::Agent.framework_manager.get_route_information(@request)
        append_route_coverage(route_dtm)
        append_to_new_observed_route(new_route_coverage_dtm)
      end
    end
  end
end