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

require 'base64'
require 'contrast/agent/assess/contrast_object'
require 'contrast/agent/reporting/reporting_events/reportable_hash'

module Contrast
  module Agent
    module Reporting
      # This is the new FindingEventObject class which will include all the needed information for the new reporting
      # system to relay this information in the Finding/Trace messages. These FindingEventObjects are used by
      # TeamServer to construct the vulnerability information for the assess feature. They represent those parts of the
      # objects that were acted on in a Dataflow Finding.
      class FindingEventObject < Contrast::Agent::Reporting::ReportableHash
        # @return [Integer] the id of the Object this represents.
        attr_reader :hash
        # @return [Boolean] if the Object is tracked or not
        attr_reader :tracked
        # @return [String] the base64 of the human readable representation of the Object this represents.
        attr_reader :value

        # We'll truncate any object that isn't important to the taint ranges of this event, so that we don't murder
        # TeamServer by, for instance, hypothetically sending the entire rendered HTML page >_>  <_<  >_>
        ELLIPSIS = '...'
        UNTRUNCATED_PORTION_LENGTH = 25
        TRUNCATION_LENGTH = (UNTRUNCATED_PORTION_LENGTH * 2) + ELLIPSIS.length

        class << self
          # @param object [Contrast::Agent::Assess::ContrastObject] the object to translate
          # @param truncate [Boolean] if the value of this FindingEventObject should be truncated or not
          # @return [Contrast::Agent::Reporting::FindingEventObject]
          def convert object, truncate
            report = new
            report.attach_data(object, truncate)
            report
          end
        end

        # Parse the data from a Contrast::Agent::Assess::ContrastObject to attach what is required for reporting to
        # TeamServer to this Contrast::Agent::Reporting::FindingEventObject
        #
        # @param object [Contrast::Agent::Assess::ContrastObject, nil]
        def attach_data object, truncate
          @hash = object ? object.tracked_object_id : nil.__id__
          @tracked = !!object&.tracked?
          @value = reportable_value(object&.object, truncate)
        end

        # Convert the instance variables on the class, and other information, into the identifiers required for
        # TeamServer to process the JSON form of this message.
        #
        # @return [Hash]
        # @raise [ArgumentError]
        def to_controlled_hash
          validate
          {
              hash: hash,
              tracked: tracked,
              value: value
          }
        end

        # @raise [ArgumentError]
        def validate
          raise(ArgumentError, "#{ self } did not have a proper hash. Unable to continue.") unless hash
          raise(ArgumentError, "#{ self } did not have a proper tracked. Unable to continue.") if tracked.nil?
          return if value

          raise(ArgumentError, "#{ self } did not have a proper value. Unable to continue.")
        end

        private

        # Parse, truncate, and translate the given value to be reported to TeamServer. The field is expected to be
        # base64 encoded.
        #
        # @param value [String, nil] the contrast_string of the object this represents.
        # @param truncate [Boolean] if the string should be truncated or not.
        # @return [String] The strict_encode64 value of the String sent to TeamServer to represent this object.
        def reportable_value value, truncate
          return Contrast::Utils::ObjectShare::NIL_64_STRING unless value

          if truncate && value.length > TRUNCATION_LENGTH
            tmp = []
            tmp << value[0, UNTRUNCATED_PORTION_LENGTH]
            tmp << ELLIPSIS
            tmp << value[value.length - UNTRUNCATED_PORTION_LENGTH, UNTRUNCATED_PORTION_LENGTH]
            value = tmp.join
          end
          Base64.strict_encode64(value)
        end
      end
    end
  end
end