import json
from datetime import datetime

import hashlib
import hmac
from base64 import b64encode
from django.utils.timezone import utc

from .base import AnymailBaseWebhookView
from ..exceptions import AnymailWebhookValidationFailure, AnymailConfigurationError
from ..signals import tracking, AnymailTrackingEvent, EventType
from ..utils import get_anymail_setting, getfirst


class MandrillSignatureMixin(object):
    """Validates Mandrill webhook signature"""

    # These can be set from kwargs in View.as_view, or pulled from settings in init:
    webhook_key = None  # required
    webhook_url = None  # optional; defaults to actual url used

    def __init__(self, **kwargs):
        # noinspection PyUnresolvedReferences
        esp_name = self.esp_name
        webhook_key = get_anymail_setting('webhook_key', esp_name=esp_name,
                                          kwargs=kwargs, allow_bare=True)
        self.webhook_key = webhook_key.encode('ascii')  # hmac.new requires bytes key in python 3
        self.webhook_url = get_anymail_setting('webhook_url', esp_name=esp_name, default=None,
                                               kwargs=kwargs, allow_bare=True)
        # noinspection PyArgumentList
        super(MandrillSignatureMixin, self).__init__(**kwargs)

    def validate_request(self, request):
        try:
            signature = request.META["HTTP_X_MANDRILL_SIGNATURE"]
        except KeyError:
            raise AnymailWebhookValidationFailure("X-Mandrill-Signature header missing from webhook POST")

        # Mandrill signs the exact URL plus the sorted POST params:
        signed_data = self.webhook_url or request.build_absolute_uri()
        params = request.POST.dict()
        for key in sorted(params.keys()):
            signed_data += key + params[key]

        expected_signature = b64encode(hmac.new(key=self.webhook_key, msg=signed_data.encode('utf-8'),
                                                digestmod=hashlib.sha1).digest())
        if not hmac.compare_digest(signature, expected_signature):
            raise AnymailWebhookValidationFailure("Mandrill webhook called with incorrect signature")


class MandrillBaseWebhookView(MandrillSignatureMixin, AnymailBaseWebhookView):
    """Base view class for Mandrill webhooks"""

    warn_if_no_basic_auth = False  # because we validate against signature

    def parse_events(self, request):
        esp_events = json.loads(request.POST['mandrill_events'])
        return [self.esp_to_anymail_event(esp_event) for esp_event in esp_events]

    def esp_to_anymail_event(self, esp_event):
        raise NotImplementedError()


class MandrillTrackingWebhookView(MandrillBaseWebhookView):

    signal = tracking

    event_types = {
        # Message events:
        'send': EventType.SENT,
        'deferral': EventType.DEFERRED,
        'hard_bounce': EventType.BOUNCED,
        'soft_bounce': EventType.DEFERRED,
        'open': EventType.OPENED,
        'click': EventType.CLICKED,
        'spam': EventType.COMPLAINED,
        'unsub': EventType.UNSUBSCRIBED,
        'reject': EventType.REJECTED,
        # Sync events (we don't really normalize these well):
        'whitelist': EventType.UNKNOWN,
        'blacklist': EventType.UNKNOWN,
        # Inbound events:
        'inbound': EventType.INBOUND,
    }

    def esp_to_anymail_event(self, esp_event):
        esp_type = getfirst(esp_event, ['event', 'type'], None)
        event_type = self.event_types.get(esp_type, EventType.UNKNOWN)
        if event_type == EventType.INBOUND:
            raise AnymailConfigurationError(
                "You seem to have set Mandrill's *inbound* webhook URL "
                "to Anymail's Mandrill *tracking* webhook URL.")

        try:
            timestamp = datetime.fromtimestamp(esp_event['ts'], tz=utc)
        except (KeyError, ValueError):
            timestamp = None

        try:
            recipient = esp_event['msg']['email']
        except KeyError:
            try:
                recipient = esp_event['reject']['email']  # sync events
            except KeyError:
                recipient = None

        try:
            mta_response = esp_event['msg']['diag']
        except KeyError:
            mta_response = None

        try:
            description = getfirst(esp_event['reject'], ['detail', 'reason'])
        except KeyError:
            description = None

        try:
            metadata = esp_event['msg']['metadata']
        except KeyError:
            metadata = None

        try:
            tags = esp_event['msg']['tags']
        except KeyError:
            tags = None

        return AnymailTrackingEvent(
            click_url=esp_event.get('url', None),
            description=description,
            esp_event=esp_event,
            event_type=event_type,
            message_id=esp_event.get('_id', None),
            metadata=metadata,
            mta_response=mta_response,
            recipient=recipient,
            reject_reason=None,  # probably map esp_event['msg']['bounce_description'], but insufficient docs
            tags=tags,
            timestamp=timestamp,
            user_agent=esp_event.get('user_agent', None),
        )
