require "resque"
require "resque/failure/multiple"

module Bugsnag
  class Resque < ::Resque::Failure::Base

    FRAMEWORK_ATTRIBUTES = {
      :framework => "Resque"
    }

    ##
    # Callthrough to Bugsnag configuration.
    def self.configure(&block)
      add_failure_backend
      Bugsnag.configure(&block)
    end

    ##
    # Sets up the Resque failure backend.
    def self.add_failure_backend
      return if ::Resque::Failure.backend == self

      # Ensure resque is using a "Multiple" failure backend
      unless ::Resque::Failure.backend <= ::Resque::Failure::Multiple
        original_backend = ::Resque::Failure.backend
        ::Resque::Failure.backend = ::Resque::Failure::Multiple
        ::Resque::Failure.backend.classes ||= []
        ::Resque::Failure.backend.classes << original_backend
      end

      # Add Bugsnag failure backend
      unless ::Resque::Failure.backend.classes.include?(self)
        ::Resque::Failure.backend.classes << self
      end
    end

    ##
    # Notifies Bugsnag of a raised exception.
    def save
      Bugsnag.notify(exception, true) do |report|
        report.severity = "error"
        report.severity_reason = {
          :type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
          :attributes => FRAMEWORK_ATTRIBUTES
        }

        metadata = payload
        class_name = metadata['class']

        # when using Active Job the payload "class" will always be the Resque
        # "JobWrapper", so we need to unwrap the actual class name
        if class_name == "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper"
          unwrapped_class_name = metadata['args'][0]['job_class'] rescue nil

          if unwrapped_class_name
            class_name = unwrapped_class_name
            metadata['wrapped'] ||= unwrapped_class_name
          end
        end

        context = "#{class_name}@#{queue}"
        report.meta_data.merge!({ context: context, payload: metadata })
        report.automatic_context = context
      end
    end
  end
end

# For backwards compatibility
Resque::Failure::Bugsnag = Bugsnag::Resque

# Auto-load the failure backend
Bugsnag::Resque.add_failure_backend

worker = Resque::Worker.new(:bugsnag_fork_check)

# If at_exit hooks are not enabled then we can't use the thread queue delivery
# method because it relies on an at_exit hook to ensure the queue is flushed
can_use_thread_queue = worker.respond_to?(:run_at_exit_hooks) && worker.run_at_exit_hooks
default_delivery_method = can_use_thread_queue ? :thread_queue : :synchronous

if worker.fork_per_job?
  Resque.after_fork do
    Bugsnag.configuration.detected_app_type = "resque"
    Bugsnag.configuration.default_delivery_method = default_delivery_method
    Bugsnag.configuration.runtime_versions["resque"] = ::Resque::VERSION
  end
else
  Resque.before_first_fork do
    Bugsnag.configuration.detected_app_type = "resque"
    Bugsnag.configuration.default_delivery_method = default_delivery_method
    Bugsnag.configuration.runtime_versions["resque"] = ::Resque::VERSION
  end
end
