from __future__ import absolute_import

import six

from celery.signals import task_prerun, task_postrun
from django.conf import settings
from six.moves.urllib.parse import urlparse
from time import time

from sentry.net.http import SafeSession
from sentry.utils.sdk import capture_exception, configure_scope


def get_api_root_from_dsn(dsn):
    if not dsn:
        return
    parsed = urlparse(dsn)
    if parsed.port:
        return u"{}://{}:{}".format(parsed.scheme, parsed.hostname, parsed.port)
    return u"{}://{}".format(parsed.scheme, parsed.hostname)


SENTRY_DSN = settings.SENTRY_MONITOR_DSN

API_ROOT = settings.SENTRY_MONITOR_API_ROOT or get_api_root_from_dsn(SENTRY_DSN)


def connect(app):
    task_prerun.connect(report_monitor_begin, weak=False)
    task_postrun.connect(report_monitor_complete, weak=False)

    # XXX(dcramer): Celery docs suggest it should be app.conf.beat_schedule, which
    # was likely a change in 4.x. This code is intended to support "any celery" and be
    # adopted into sentry-sdk core, thus we support it here.
    schedule = (
        app.conf.beat_schedule
        if hasattr(app.conf, "beat_schedule")
        else app.conf["CELERYBEAT_SCHEDULE"]
    )
    for schedule_name, monitor_id in six.iteritems(settings.SENTRY_CELERYBEAT_MONITORS):
        schedule[schedule_name].setdefault("options", {}).setdefault("headers", {}).setdefault(
            "X-Sentry-Monitor", monitor_id
        )


# Celery signals fail to propagate if they error and we dont want to break things with our
# instrumentation
# XXX(dcramer): This appears to be specific to the 3.x series we're running, and doesn't have
# the same behavior in 4.x
def suppress_errors(func):
    def inner(*a, **k):
        try:
            return func(*a, **k)
        except Exception:
            capture_exception()

    return inner


@suppress_errors
def report_monitor_begin(task, **kwargs):
    if not SENTRY_DSN or not API_ROOT:
        return

    monitor_id = task.request.headers.get("X-Sentry-Monitor")
    if not monitor_id:
        return

    with configure_scope() as scope:
        scope.set_context("monitor", {"id": monitor_id})

    session = SafeSession()
    req = session.post(
        u"{}/api/0/monitors/{}/checkins/".format(API_ROOT, monitor_id),
        headers={"Authorization": u"DSN {}".format(SENTRY_DSN)},
        json={"status": "in_progress"},
    )
    req.raise_for_status()
    # HACK:
    task.request.headers["X-Sentry-Monitor-CheckIn"] = (req.json()["id"], time())


@suppress_errors
def report_monitor_complete(task, retval, **kwargs):
    if not SENTRY_DSN or not API_ROOT:
        return

    monitor_id = task.request.headers.get("X-Sentry-Monitor")
    if not monitor_id:
        return

    try:
        checkin_id, start_time = task.request.headers.get("X-Sentry-Monitor-CheckIn")
    except (ValueError, TypeError):
        return

    duration = int((time() - start_time) * 1000)

    session = SafeSession()
    session.put(
        u"{}/api/0/monitors/{}/checkins/{}/".format(API_ROOT, monitor_id, checkin_id),
        headers={"Authorization": u"DSN {}".format(SENTRY_DSN)},
        json={"status": "error" if isinstance(retval, Exception) else "ok", "duration": duration},
    ).raise_for_status()
