import re
import hashlib
import hmac
import json
import logging
import base64

from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.http import HttpRequest
from django.utils.encoding import smart_bytes

from ..backends import RequestParser
from ..errors import (
    RequestParseError,
    AttachmentTooLargeError,
    AuthenticationError,
)

logger = logging.getLogger(__name__)


class MandrillSignatureMismatchError(AuthenticationError):
    """Error raised when the request's mandrill signature doesn't match.
    """

    def __init__(self, request, expected, calculated):
        super(MandrillSignatureMismatchError, self)
        self.request = request
        self.expected_signature = expected
        self.calculated_signature = calculated


def _detect_base64(s):
    """Quite an ingenuous function to guess if a string is base64 encoded
    """
    return (len(s) % 4 == 0) and re.match('^[A-Za-z0-9+/]+[=]{0,2}$', s)


def _check_mandrill_signature(request, key):
    expected = request.META.get('HTTP_X_MANDRILL_SIGNATURE', None)
    url = request.build_absolute_uri()
    # Mandrill appends the POST params in alphabetical order of the key.
    params = sorted(request.POST.items(), key=lambda x: x[0])
    message = url + ''.join(key + value for key, value in params)
    signed_binary = hmac.new(
        key.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha1,
    )
    signature = base64.b64encode(signed_binary.digest()).decode('utf-8')
    if signature != expected:
        raise MandrillSignatureMismatchError(request, expected, signature)


class MandrillRequestParser(RequestParser):
    """Mandrill request parser. """

    def _process_attachments(self, email, attachments):
        for key, attachment in list(attachments.items()):
            is_base64 = attachment.get('base64')
            name = attachment.get('name')
            mimetype = attachment.get('type')
            content = attachment.get('content', "")

            if is_base64:
                content = base64.b64decode(content)
            # watchout: sometimes attachment contents are base64'd but mandrill doesn't set the flag
            elif _detect_base64(content):
                content = base64.b64decode(content)

            content = smart_bytes(content, strings_only=True)

            if len(content) > self.max_file_size:
                logger.debug(
                    "File attachment %s is too large to process (%sB)",
                    name,
                    len(content)
                )
                raise AttachmentTooLargeError(
                    email=email,
                    filename=name,
                    size=len(content)
                )

            if name and mimetype and content:
                email.attach(name, content, mimetype)
        return email

    def _get_recipients(self, array):
        """Returns an iterator of objects
           in the form ["Name <address@example.com", ...]
           from the array [["address@example.com", "Name"]]
        """
        for address, name in array:
            if not name:
                yield address
            else:
                yield "\"%s\" <%s>" % (name, address)

    def _get_sender(self, from_email, from_name=None):
        if not from_name:
            return from_email
        else:
            return "\"%s\" <%s>" % (from_name, from_email)

    def parse(self, request):
        """Parse incoming request and return an email instance.

        Args:
            request: an HttpRequest object, containing a list of forwarded emails, as
                per Mandrill specification for inbound emails.

        Returns:
            a list of EmailMultiAlternatives instances
        """
        assert isinstance(request, HttpRequest), "Invalid request type: %s" % type(request)

        if settings.INBOUND_MANDRILL_AUTHENTICATION_KEY:
            _check_mandrill_signature(
                request=request,
                key=settings.INBOUND_MANDRILL_AUTHENTICATION_KEY,
            )

        try:
            messages = json.loads(request.POST['mandrill_events'])
        except (ValueError, KeyError) as ex:
            raise RequestParseError("Request is not a valid json: %s" % ex)

        if not messages:
            logger.debug("No messages found in mandrill request: %s", request.body)
            return []

        emails = []
        for message in messages:
            if message.get('event') != 'inbound':
                logger.debug("Discarding non-inbound message")
                continue

            msg = message.get('msg')
            try:
                from_email = msg['from_email']
                to = list(self._get_recipients(msg['to']))
                cc = list(self._get_recipients(msg['cc'])) if 'cc' in msg else []
                bcc = list(self._get_recipients(msg['bcc'])) if 'bcc' in msg else []

                subject = msg.get('subject', "")

                attachments = msg.get('attachments', {})
                attachments.update(msg.get('images', {}))

                text = msg.get('text', "")
                html = msg.get('html', "")
            except (KeyError, ValueError) as ex:
                raise RequestParseError(
                    "Inbound request is missing or got an invalid value.: %s." % ex
                )

            email = EmailMultiAlternatives(
                subject=subject,
                body=text,
                from_email=self._get_sender(
                    from_email=from_email,
                    from_name=msg.get('from_name'),
                ),
                to=to,
                cc=cc,
                bcc=bcc,
            )
            if html is not None and len(html) > 0:
                email.attach_alternative(html, "text/html")

            email = self._process_attachments(email, attachments)
            emails.append(email)

        return emails