from os import path

from django.core.mail import EmailMultiAlternatives
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.mailgun import MailgunRequestParser
from ..errors import RequestParseError, AttachmentTooLargeError

from .test_files.mailgun_post import test_inbound_payload as mailgun_payload


class MailgunRequestParserTests(TestCase):
    """Tests for MailRequestParser - NB test use parser direct, not via view."""

    def _assertEmailParsedCorrectly(self, email, data):
        """Helper assert method that matches email properties to posted data.

        It's pulled out here as this is used in most of the tests to assert that
        the parser worked properly.

        Args:
            email: the EmailMultiAlternatives object to test.
            data: a dict of data that was sent to the parser.

        """
        self.assertIsInstance(email, EmailMultiAlternatives)
        self.assertEqual(email.to, data.get('recipient', '').split(','))
        self.assertEqual(email.from_email, data.get('sender', ''))
        self.assertEqual(email.subject, data.get('subject', ''))
        self.assertEqual(email.body, "%s\n\n%s" % (
            data.get('stripped-text', ''),
            data.get('stripped-signature', '')
        ))
        self.assertEqual(email.cc, data.get('cc', '').split(','))
        self.assertEqual(email.bcc, data.get('bcc', '').split(','))
        if 'html' in data:
            self.assertEqual(len(email.alternatives), 1)
            self.assertEqual(email.alternatives[0][0], data.get('stripped-html', ''))

    def setUp(self):
        # Every test needs access to the request factory.
        self.url = reverse('receive_inbound_email')
        self.factory = RequestFactory()
        self.parser = MailgunRequestParser()
        self.test_upload_txt = path.join(path.dirname(__file__), 'test_files/test_upload_file.txt')
        self.test_upload_png = path.join(path.dirname(__file__), 'test_files/test_upload_file.jpg')

    def test_parse_valid_request(self):
        """Test that a valid POST returns a 200."""
        request = self.factory.post(self.url, data=mailgun_payload)
        email = self.parser.parse(request)
        self._assertEmailParsedCorrectly(email, mailgun_payload)

    def test_parse_invalid_request(self):
        """Test that an invalid request raises RequestParseError."""
        request = self.factory.post(self.url, data={})
        self.assertRaises(RequestParseError, self.parser.parse, request)

    def test_text_email_only(self):
        """Test inbound email with no HTML alternative."""
        data = mailgun_payload
        del data['stripped-html']
        request = self.factory.post(self.url, data=data)
        email = self.parser.parse(request)
        self._assertEmailParsedCorrectly(email, mailgun_payload)

    def test_attachments(self):
        """Test inbound email with attachments."""
        data = mailgun_payload
        attachment_1 = open(self.test_upload_txt, 'r').read()
        attachment_2 = open(self.test_upload_png, 'rb').read()
        data['attachment-1'] = open(self.test_upload_txt, 'r')
        data['attachment-2'] = open(self.test_upload_png, 'rb')
        request = self.factory.post(self.url, data=data)
        email = self.parser.parse(request)

        self._assertEmailParsedCorrectly(email, data)

        # for each attachmen, check the contents match the input
        self.assertEqual(len(email.attachments), 2)

        # convert list of 3-tuples into dict so we can lookup by filename
        attachments = {k[0]: (k[1], k[2]) for k in email.attachments}
        self.assertEqual(smart_bytes(attachments['attachment-1'][0]), smart_bytes(attachment_1))
        self.assertEqual(attachments['attachment-1'][1], 'text/plain')
        self.assertEqual(attachments['attachment-2'][0], attachment_2)
        self.assertEqual(attachments['attachment-2'][1], 'image/jpeg')

    @override_settings(INBOUND_EMAIL_ATTACHMENT_SIZE_MAX=0)
    def test_attachments_max_size(self):
        """Test inbound email attachment max size limit."""
        # receive an email
        data = mailgun_payload
        open(self.test_upload_txt, 'rb').read()
        open(self.test_upload_png, 'rb').read()
        data['attachment-1'] = open(self.test_upload_txt, 'rb')
        data['attachment-2'] = open(self.test_upload_png, 'rb')
        request = self.factory.post(self.url, data=data)

        # should except
        with self.assertRaises(AttachmentTooLargeError):
            self.parser.parse(request),