# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require 'new_relic/agent/transaction/tracing'
require 'new_relic/agent/distributed_tracing/cross_app_tracing'
require 'new_relic/agent/distributed_tracing/cross_app_payload'

module NewRelic
  module Agent
    #
    # This module contains helper methods to facilitate
    # instrumentation of external requests not directly supported by
    # the Ruby agent. It is intended to be primarily used by authors
    # of 3rd-party instrumentation.
    #
    # @api public
    module External
      extend self

      NON_HTTP_CAT_ID_HEADER = 'NewRelicID'.freeze
      NON_HTTP_CAT_TXN_HEADER = 'NewRelicTransaction'.freeze
      NON_HTTP_CAT_SYNTHETICS_HEADER = 'NewRelicSynthetics'.freeze
      NON_HTTP_CAT_CONTENT_LENGTH = -1

      # Process obfuscated +String+ identifying a calling application and transaction that is also running a
      # New Relic agent and save information in current transaction for inclusion in a trace. The +String+ is
      # generated by +get_request_metadata+ on the calling application.
      #
      # @param request_metadata [String] received obfuscated request metadata
      #
      # @api public
      #
      def process_request_metadata(request_metadata)
        NewRelic::Agent.record_api_supportability_metric(:process_request_metadata)
        return unless CrossAppTracing.cross_app_enabled?

        state = NewRelic::Agent::Tracer.state
        if transaction = state.current_transaction
          rmd = ::JSON.parse(obfuscator.deobfuscate(request_metadata))

          # handle/check ID
          #
          if id = rmd[NON_HTTP_CAT_ID_HEADER] and CrossAppTracing.trusted_valid_cross_app_id?(id)
            # handle transaction info
            #
            if txn_info = rmd[NON_HTTP_CAT_TXN_HEADER]
              payload = CrossAppPayload.new(id, transaction, txn_info)
              transaction.distributed_tracer.cross_app_payload = payload

              CrossAppTracing.assign_intrinsic_transaction_attributes(state)
            end

            # handle synthetics
            #
            if synth = rmd[NON_HTTP_CAT_SYNTHETICS_HEADER]
              transaction.synthetics_payload = synth
              transaction.raw_synthetics_header = obfuscator.obfuscate(::JSON.dump(synth))
            end

          else
            NewRelic::Agent.logger.error("error processing request metadata: invalid/non-trusted ID: '#{id}'")
          end

          nil
        end
      rescue => e
        NewRelic::Agent.logger.error('error during process_request_metadata', e)
      end

      # Obtain an obfuscated +String+ suitable for delivery across public networks that carries transaction
      # information from this application to a calling application which is also running a New Relic agent.
      # This +String+ can be processed by +process_response_metadata+ on the calling application.
      #
      # @return [String] obfuscated response metadata to send
      #
      # @api public
      #
      def get_response_metadata
        NewRelic::Agent.record_api_supportability_metric(:get_response_metadata)
        return unless CrossAppTracing.cross_app_enabled?

        return unless (txn = Tracer.current_transaction)
        return unless (payload = txn.distributed_tracer.cross_app_payload)

        # must freeze the name since we're responding with it
        #
        txn.freeze_name_and_execute_if_not_ignored do
          # build response payload
          #
          rmd = {
            NewRelicAppData: payload.as_json_array(NON_HTTP_CAT_CONTENT_LENGTH)
          }

          # obfuscate the generated response metadata JSON
          #
          obfuscator.obfuscate(::JSON.dump(rmd))
        end
      rescue => e
        NewRelic::Agent.logger.error('error during get_response_metadata', e)
      end

      private

      def obfuscator
        CrossAppTracing.obfuscator
      end
    end
  end
end
