import logging
from os import environ

import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware

from odoo import http
from odoo.addons.base.models.ir_cron import ir_cron
from odoo.http import HttpDispatcher, JsonRPCDispatcher

from .exceptions import FastForwardError, MergeError, Unmergeable


def delegate(self, attr):
    return getattr(self.app, attr)
SentryWsgiMiddleware.__getattr__ = delegate

def enable_sentry():
    logger = logging.getLogger('runbot_merge')

    dsn = environ.get('SENTRY_DSN')
    if not dsn:
        logger.info("No DSN found, skipping sentry...")
        return

    try:
        setup_sentry(dsn)
    except Exception:
        logger.exception("DSN found, failed to enable sentry...")
    else:
        logger.info("DSN found, sentry enabled...")


def setup_sentry(dsn):
    sentry_sdk.init(
        dsn,
        auto_session_tracking=False,
        # traces_sample_rate=1.0,
        integrations=[
            # note: if the colorformatter is enabled, sentry gets lost
            # and classifies everything as errors because it fails to
            # properly classify levels as the colorformatter injects
            # the ANSI color codes right into LogRecord.levelname
            LoggingIntegration(level=logging.INFO, event_level=logging.WARNING),
        ],
        before_send=event_filter,
        # apparently not in my version of the sdk
        # functions_to_trace = []
    )
    http.root = SentryWsgiMiddleware(http.root)
    instrument_odoo()

def instrument_odoo():
    """Monkeypatches odoo core to copy odoo metadata into sentry for more
    informative events
    """
    # add user to wsgi request context
    for d in [HttpDispatcher, JsonRPCDispatcher]:
        def dispatch(self, endpoint, args, old_dispatch=d.dispatch):
            if self.request.uid:
                sentry_sdk.set_user({
                    'id': self.request.uid,
                    'email': self.request.env.user.email,
                    'username': self.request.env.user.login,
                })
            else:
                sentry_sdk.set_user({'username': '<public>'})
            return old_dispatch(self, endpoint, args)
        d.dispatch = dispatch

    # create transaction for tracking crons, add user to that
    old_callback = ir_cron._callback
    def _callback(self, cron_name, server_action_id, job_id):
        sentry_sdk.start_transaction(name=f"cron {cron_name}")
        sentry_sdk.set_user({
            'id': self.env.user.id,
            'email': self.env.user.email,
            'username': self.env.user.login,
        })
        return old_callback(self, cron_name, server_action_id, job_id)
    ir_cron._callback = _callback

dummy_record = logging.LogRecord(name="", level=logging.NOTSET, pathname='', lineno=0, msg='', args=(), exc_info=None)
# mapping of exception types to predicates, if the predicate returns `True` the
# exception event should be suppressed
SUPPRESS_EXCEPTION = {
    # Someone else deciding to push directly to the branch (which is generally
    # what leads to this error) is not really actionable.
    #
    # Other possibilities are more structural and thus we probably want to know:
    # - other 422 Unprocessable github errors (likely config issues):
    #   - reference does not exist
    #   - object does not exist
    #   - object is not a commit
    #   - branch protection issue
    # - timeout on ref update (github probably dying)
    # - other HTTP error (also github probably dying)
    #
    # might be worth using richer exceptions to make this clearer, and easier to classify
    FastForwardError: lambda e: 'not a fast forward' in str(e.__cause__),
    # Git conflict when merging (or non-json response which is weird),
    # notified on PR
    MergeError: lambda _: True,
    # Failed preconditions on merging, notified on PR
    Unmergeable: lambda _: True,
}
def event_filter(event, hint):
    # event['level'], event['logger'], event['logentry'], event['exception']
    # known hints: log_record: LogRecord, exc_info: (type, BaseExeption, Traceback) | None
    exc_info = hint.get('exc_info') or hint.get('log_record', dummy_record).exc_info
    if exc_info:
        etype, exc, _ = exc_info
        if SUPPRESS_EXCEPTION.get(etype, lambda _: False)(exc):
            return None