# frozen_string_literal: true

module Appsignal
  module Integrations
    # @!visibility private
    class DelayedJobPlugin < ::Delayed::Plugin
      extend Appsignal::Hooks::Helpers

      callbacks do |lifecycle|
        lifecycle.around(:invoke_job) do |job, &block|
          invoke_with_instrumentation(job, block)
        end

        lifecycle.after(:execute) do |_execute|
          Appsignal.stop("delayed job")
        end
      end

      def self.invoke_with_instrumentation(job, block)
        transaction =
          Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)

        Appsignal.instrument("perform_job.delayed_job") do
          block.call(job)
        end
      rescue Exception => error # rubocop:disable Lint/RescueException
        transaction.set_error(error)
        raise
      ensure
        payload = job.payload_object
        if payload.respond_to? :job_data
          # ActiveJob
          job_data = payload.job_data
          transaction.set_action_if_nil("#{job_data["job_class"]}#perform")
          transaction.add_params_if_nil(job_data.fetch("arguments", {}))
        else
          # Delayed Job
          transaction.set_action_if_nil(action_name_from_payload(payload, job.name))
          transaction.add_params_if_nil(extract_value(payload, :args, {}))
        end

        transaction.add_tags(
          :id => extract_value(job, :id, nil, true),
          :queue => extract_value(job, :queue),
          :priority => extract_value(job, :priority, 0),
          :attempts => extract_value(job, :attempts, 0)
        )

        transaction.set_queue_start(extract_value(job, :run_at)&.to_i&.* 1_000)

        Appsignal::Transaction.complete_current!
      end

      def self.action_name_from_payload(payload, default_name)
        # Attempt to find appsignal_name override
        class_and_method_name = extract_value(payload, :appsignal_name, nil)
        return class_and_method_name if class_and_method_name.is_a?(String)
        return default_name if default_name.split("#").length == 2
        return default_name if default_name.split(".").length == 2

        "#{default_name}#perform"
      end

      def self.extract_value(object_or_hash, field, default_value = nil, convert_to_s = false)
        value = nil

        # Attempt to read value from hash
        if object_or_hash.respond_to?(:[])
          value = begin
            object_or_hash[field]
          rescue NameError
            nil
          end
        end

        # Attempt to read value from object
        value = object_or_hash.send(field) if value.nil? && object_or_hash.respond_to?(field)

        # Set default value if nothing was found
        value = default_value if value.nil?

        if convert_to_s
          value.to_s
        else
          value
        end
      end
    end
  end
end
