import hashlib
import hmac
import json
import base64

from django.test import TestCase, override_settings
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import smart_bytes

from ..backends.mandrill import (
    MandrillRequestParser,
    MandrillSignatureMismatchError,
)
from ..errors import RequestParseError, AttachmentTooLargeError

from .test_files.mandrill_post import (
    post_data as mandrill_payload,
    post_data_with_attachments as mandrill_payload_with_attachments,
    post_data_with_attachments_mailbox as mandrill_payload_with_attachments_mailbox,
    post_data_with_attachments_mailbox_2 as mandrill_payload_with_attachments_mailbox_2,
)


class MandrillRequestParserTests(TestCase):

    def setUp(self):
        self.url = reverse('receive_inbound_email')
        self.factory = RequestFactory()
        self.parser = MandrillRequestParser()
        self.payload = mandrill_payload
        self.payload_with_attachments = mandrill_payload_with_attachments

    def _assertEmailParsedCorrectly(self, emails, mandrill_payload, has_html=True):
        def _parse_emails(to):
            ret = []
            for address, name in to:
                if not name:
                    ret.append(address)
                else:
                    ret.append("\"%s\" <%s>" % (name, address))
            return ret

        def _parse_from(name, email):
            if not name:
                return email
            return "\"%s\" <%s>" % (name, email)

        for i, e in enumerate(emails):
            msg = json.loads(mandrill_payload['mandrill_events'])[i]['msg']
            self.assertEqual(e.subject, msg['subject'])
            self.assertEqual(e.to, _parse_emails(msg['to']))
            self.assertEqual(e.cc, _parse_emails(msg['cc']))
            self.assertEqual(e.bcc, _parse_emails(msg['bcc']))
            self.assertEqual(
                e.from_email,
                _parse_from(msg.get('from_name'), msg.get('from_email'))
            )
            if has_html:
                self.assertEqual(e.alternatives[0][0], msg['html'])
            for name, contents, mimetype in e.attachments:
                # Check that base64 contents are decoded
                is_base64 = msg['attachments'][name].get('base64')
                req_contents = msg['attachments'][name]['content']
                if is_base64:
                    req_contents = base64.b64decode(req_contents)
                self.assertEqual(smart_bytes(req_contents), smart_bytes(contents))
                self.assertEqual(msg['attachments'][name]['type'], mimetype)
            self.assertEqual(e.body, msg['text'])

    def _calculate_signature(self, url, data, key):
        # Mandrill appends the POST params in alphabetical order of the key.
        params = sorted(data, 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())
        return signature.decode('utf-8')

    def test_parse_valid_request__with_attachments__from_mailbox(self):
        "Test email sent via Mailbox app without body text "
        request = self.factory.post(self.url, data=mandrill_payload_with_attachments_mailbox)
        emails = self.parser.parse(request)
        email = emails[0]
        payload = json.loads(mandrill_payload_with_attachments_mailbox['mandrill_events'])

        self.assertEqual(len(email.attachments), 1)
        self.assertEqual(email.attachments[0][0], '3c8e4ffb-6366-4351-813f-d0f600ed720e')
        self.assertEqual(email.attachments[0][2], 'image/jpeg')
        self.assertEqual(
            email.attachments[0][1],
            # the content is base64-decoded, even if the base64 flag is off in the request
            base64.b64decode(
                payload[0]['msg']['images']
                ['3c8e4ffb-6366-4351-813f-d0f600ed720e']['content']
            )
        )

    def test_parse_valid_request__with_attachments__from_mailbox__2(self):
        "Test email sent via Mailbox app with text "
        request = self.factory.post(self.url, data=mandrill_payload_with_attachments_mailbox_2)
        emails = self.parser.parse(request)
        email = emails[0]
        payload = json.loads(mandrill_payload_with_attachments_mailbox_2['mandrill_events'])

        self.assertEqual(len(email.attachments), 1)
        self.assertEqual(email.attachments[0][0], '7e357447-3f2e-4c12-a643-5720f30ca7af')
        self.assertEqual(email.attachments[0][2], 'image/jpeg')
        self.assertEqual(
            email.attachments[0][1],
            # the content is base64-decoded, even if the base64 flag is off in the request
            base64.b64decode(
                payload[0]['msg']['images']
                ['7e357447-3f2e-4c12-a643-5720f30ca7af']['content']
            )
        )

    def test_parse_valid_request(self):
        """
        Test a valid POST returns 200 and email(s) are correctly parsed
        """
        request = self.factory.post(self.url, data=mandrill_payload)
        emails = self.parser.parse(request)
        self._assertEmailParsedCorrectly(emails, mandrill_payload)

    def test_parse_valid_request__with_attachments(self):
        request = self.factory.post(self.url, data=mandrill_payload_with_attachments)
        emails = self.parser.parse(request)
        self._assertEmailParsedCorrectly(emails, mandrill_payload_with_attachments)

    @override_settings(INBOUND_MANDRILL_AUTHENTICATION_KEY='mandrill_key')
    def test_parse_valid_request__with_signature(self):
        signature = self._calculate_signature(
            url='http://testserver' + self.url,
            data=mandrill_payload.items(),
            key='mandrill_key',
        )
        request = self.factory.post(
            self.url,
            data=mandrill_payload,
            HTTP_X_MANDRILL_SIGNATURE=signature,
        )
        emails = self.parser.parse(request)
        self._assertEmailParsedCorrectly(emails, mandrill_payload)

    @override_settings(INBOUND_MANDRILL_AUTHENTICATION_KEY='mandrill_key')
    def test_parse_valid_request__with_invalid_signature(self):
        signature = 'invalid_signature'
        request = self.factory.post(
            self.url,
            data=mandrill_payload,
            HTTP_X_MANDRILL_SIGNATURE=signature,
        )
        self.assertRaises(
            MandrillSignatureMismatchError,
            self.parser.parse,
            request,
        )

    def _process_dump(self, foo):
        dump = json.loads(self.payload['mandrill_events'])
        foo(dump)
        return json.dumps(dump)

    def test_parse_invalid_request(self):
        """Test that an invalid request raises RequestParseError."""
        _process = lambda dump: dump[0]['msg'].pop('from_email', None)

        payload = {'mandrill_events': self._process_dump(_process)}
        request = self.factory.post(self.url, data=payload)
        self.assertRaises(RequestParseError, self.parser.parse, request)

    def test_text_email_only(self):
        """Test inbound email with no HTML alternative."""
        _process = lambda dump: dump[0]['msg'].pop('html')

        payload = {'mandrill_events': self._process_dump(_process)}
        request = self.factory.post(self.url, data=payload)
        email = self.parser.parse(request)
        self._assertEmailParsedCorrectly(email, payload, has_html=False)

    @override_settings(INBOUND_EMAIL_ATTACHMENT_SIZE_MAX=0)
    def test_attachments_max_size(self):
        """Test inbound email attachment max size limit."""
        # receive an email
        request = self.factory.post(self.url, data=self.payload_with_attachments)
        # should except
        with self.assertRaises(AttachmentTooLargeError):
            self.parser.parse(request)

    def test_correspondent_field_parsing(self):
        """Test the speific address parsing of the Mandrill backend"""
        # Addresses https://github.com/yunojuno/django-inbound-email/issues/20

        for input_data, attr_name, expected in [
            # TO / CC / BCC -- all handled by the same method: _get_recipients()
            (
                [['jed@whitehouse.gov', "Jed Bartlet"]],
                "to",
                ['"Jed Bartlet" <jed@whitehouse.gov>'],
            ),
            (
                [
                    ['jed@whitehouse.gov', "Jed Bartlet"],
                    ['toby@whitehouse.gov', "Toby Ziegler"]],
                "to",
                [
                    '"Jed Bartlet" <jed@whitehouse.gov>',
                    '"Toby Ziegler" <toby@whitehouse.gov>'
                ],
            ),
            (
                [['jed@whitehouse.gov', "Bartlet, Jed"]],
                "to",
                ['"Bartlet, Jed" <jed@whitehouse.gov>'],
            ),
            (
                [
                    ['jed@whitehouse.gov', "Bartlet, Jed"],
                    ['toby@whitehouse.gov', "Ziegler, Toby"]],
                "to",
                [
                    '"Bartlet, Jed" <jed@whitehouse.gov>',
                    '"Ziegler, Toby" <toby@whitehouse.gov>'
                ],
            ),
            (
                [['jed@whitehouse.gov', None]],
                "to",
                ['jed@whitehouse.gov'],
            ),
            (
                [
                    ['jed@whitehouse.gov', None],
                    ['toby@whitehouse.gov', None]],
                "to",
                [
                    'jed@whitehouse.gov',
                    'toby@whitehouse.gov'
                ],
            ),
            (
                [['jed@whitehouse.gov', '']],
                "to",
                ['jed@whitehouse.gov'],
            ),
            (
                [
                    ['jed@whitehouse.gov', ''],
                    ['toby@whitehouse.gov', '']
                ],
                "to",
                [
                    'jed@whitehouse.gov',
                    'toby@whitehouse.gov'
                ],
            ),

            # FROM - handled by a _get_sender() method
            # NB: 1) we do not get back a list.
            # 2) _get_sender expects email and name as args
            (
                ['jed@whitehouse.gov', "Jed Bartlet"],
                "from_email",
                '"Jed Bartlet" <jed@whitehouse.gov>',
            ),
            (
                ['jed@whitehouse.gov', "Bartlet, Jed"],
                "from_email",
                '"Bartlet, Jed" <jed@whitehouse.gov>',
            ),
            (
                ['jed@whitehouse.gov', ""],
                "from_email",
                'jed@whitehouse.gov',
            ),
            (
                ['jed@whitehouse.gov', None],
                "from_email",
                'jed@whitehouse.gov',
            ),

            # Handling edge-case or invalid content
            (
                # Latin 1 example
                [['sīla@exañple.com', "McTøst, Sīla"]],
                "to",
                ['"McTøst, Sīla" <sīla@exañple.com>'],
            ),
        ]:

            if attr_name == 'from_email':
                # _get_sender expects a two-tuple of args and returns a string
                output = self.parser._get_sender(*input_data)
            else:
                # _get_recipients expects a list of lists and returns a list of strings
                _results = self.parser._get_recipients(input_data)
                output = [x for x in _results]

            self.assertEqual(
                output,
                expected,
                "Failed to get %s for '%s' %s -- got %s" % (
                    expected, attr_name, input_data, output
                )
            )