import os
import sys
from datetime import datetime

import rq
from sentry_sdk import capture_message as sentry_capture_message
from sentry_sdk.hub import Hub
from sentry_sdk.integrations import Integration
from sentry_sdk.integrations.wsgi import _make_wsgi_event_processor
from sentry_sdk.tracing import SOURCE_FOR_STYLE
from sentry_sdk.tracing_utils import record_sql_queries
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception

import xhiveframework
import xhiveframework.monitor
from xhiveframework.database.database import Database, EmptyQueryValues


class XhiveFrameworkIntegration(Integration):
	identifier = "xhiveframework"

	@staticmethod
	def setup_once():
		real_connect = Database.connect
		real_sql = Database.sql

		def sql(self, query, values=None, *args, **kwargs):
			hub = Hub.current

			if not self._conn:
				self.connect()

			with record_sql_queries(
				hub, self._cursor, query, values, paramstyle="pyformat", executemany=False
			):
				return real_sql(self, query, values or EmptyQueryValues, *args, **kwargs)

		def connect(self):
			hub = Hub.current
			with capture_internal_exceptions():
				hub.add_breadcrumb(message="connect", category="query")

			with hub.start_span(op="db", description="connect"):
				return real_connect(self)

		Database.connect = connect
		Database.sql = sql


def set_scope(scope):
	if job := rq.get_current_job():
		kwargs = job._kwargs
		transaction_name = str(kwargs["method"])
		context = xhiveframework._dict({"scheduled": False, "wait": 0})
		if "run_scheduled_job" in transaction_name:
			transaction_name = kwargs.get("kwargs", {}).get("job_type", "")
			context.scheduled = True

		waitdiff = datetime.utcnow() - job.enqueued_at
		context.uuid = job.id
		context.wait = waitdiff.total_seconds()

		scope.set_extra("job", context)
		scope.set_transaction_name(transaction_name)
	else:
		if xhiveframework.form_dict.cmd:
			path = f"/api/method/{xhiveframework.form_dict.cmd}"
		else:
			path = xhiveframework.request.path

		scope.set_transaction_name(
			path,
			source=SOURCE_FOR_STYLE["endpoint"],
		)

	scope.set_user({"id": xhiveframework.local.site})
	user = getattr(xhiveframework.session, "user", "Unidentified")
	scope.set_tag("xhiveframework_user", user)
	# Extract `X-XhiveFramework-Request-ID` to store as a separate field if its present
	if trace_id := xhiveframework.monitor.get_trace_id():
		scope.set_tag("xhiveframework_trace_id", trace_id)


def set_sentry_context():
	if not xhiveframework.get_system_settings("enable_telemetry"):
		return

	hub = Hub.current
	with hub.configure_scope() as scope:
		set_scope(scope)


def before_send(event, hint):
	if event.get("logger", "") == "CSSUTILS":
		return None
	return event


def capture_exception(message: str | None = None) -> None:
	"""
	Function to upload exception data to entry

	:param message: A message to be sent if we can't find an exception
	"""
	# Don't report anything if the user hasn't opted-in to telemetry
	if not xhiveframework.get_system_settings("enable_telemetry"):
		return
	try:
		hub = Hub.current
		with hub.configure_scope() as scope:
			if (
				os.getenv("ENABLE_SENTRY_DB_MONITORING") is None
				or os.getenv("SENTRY_TRACING_SAMPLE_RATE") is None
			):
				set_scope(scope)
			if xhiveframework.request:
				evt_processor = _make_wsgi_event_processor(xhiveframework.request.environ, False)
				scope.add_event_processor(evt_processor)
				if xhiveframework.request.is_json:
					scope.set_context("JSON Body", xhiveframework.request.json)
				elif xhiveframework.request.form:
					scope.set_context("Form Data", xhiveframework.request.form)

		if client := hub.client:
			exc_info = sys.exc_info()
			if any(exc_info):
				# Don't report validation errors
				if isinstance(exc_info[0], xhiveframework.ValidationError):
					return

				event, hint = event_from_exception(
					exc_info,
					client_options=client.options,
					mechanism={"type": "wsgi", "handled": False},
				)
				hub.capture_event(event, hint=hint)
			elif message:
				sentry_capture_message(message, level="error")

	except Exception:
		xhiveframework.logger().error("Failed to capture exception", exc_info=True)
		pass


def add_bootinfo(bootinfo):
	"""Called from hook, sends DSN so client side can setup error monitoring."""
	if not xhiveframework.get_system_settings("enable_telemetry"):
		return

	if sentry_dsn := os.getenv("XHIVEFRAMEWORK_SENTRY_DSN"):
		bootinfo.sentry_dsn = sentry_dsn
