# frozen_string_literal: true

class BlatherClient < Blather::Client
	def handle_data(stanza)
		EMPromise.resolve(nil).then {
			with_sentry(stanza) do |scope|
				Thread.current[:log] = ::LOG.child(
					transaction: scope.transaction_name
				)
				super
			rescue StandardError => e
				handle_error(scope, stanza, e)
			end
		}.catch { |e| panic(e) }
	end

	def with_sentry(stanza)
		Sentry.clone_hub_to_current_thread

		Sentry.with_scope do |scope|
			setup_scope(stanza, scope)
			yield scope
		ensure
			scope.get_transaction&.then do |tx|
				tx.set_status("ok") unless tx.status
				tx.finish
			end
		end
	end

	def setup_scope(stanza, scope)
		name = stanza.respond_to?(:node) ? stanza.node : stanza.name
		scope.clear_breadcrumbs
		scope.set_transaction_name(name)
		scope.set_user(jid: stanza.from.stripped.to_s)

		transaction = Sentry.start_transaction(
			name: name,
			op: "blather.handle_data"
		)
		scope.set_span(transaction) if transaction
	end

	def handle_error(scope, stanza, e)
		log.error(
			"Error raised during #{scope.transaction_name}: " \
			"#{e.class}",
			e
		)
		Sentry.capture_exception(e) unless e.is_a?(Sentry::Error)
		scope.get_transaction&.set_status("internal_error")
		return if e.respond_to?(:replied?) && e.replied?

		write stanza.as_error("internal-server-error", :cancel)
	end

	def call_handler(handler, guards, stanza)
		result = if guards.first.respond_to?(:to_str)
			found = stanza.find(*guards)
			throw :pass if found.empty?

			handler.call(stanza, found)
		else
			throw :pass if guarded?(guards, stanza)

			handler.call(stanza)
		end

		return result unless result.is_a?(Promise)

		result.sync
	end
end
