from datetime import date, datetime

from django.core import mail
from django.core.files.base import ContentFile
from django.conf import settings

from django.test import TestCase
from django.test.utils import override_settings

from ..settings import get_batch_size, get_log_level, get_threads_per_process
from ..models import Email, EmailTemplate, Attachment, PRIORITY, STATUS
from ..mail import (create, get_queued,
                    send, send_many, send_queued, _send_bulk)


connection_counter = 0


class ConnectionTestingBackend(mail.backends.base.BaseEmailBackend):
    '''
    An EmailBackend that increments a global counter when connection is opened
    '''

    def open(self):
        global connection_counter
        connection_counter += 1

    def send_messages(self, email_messages):
        pass


class MailTest(TestCase):

    @override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
    def test_send_queued_mail(self):
        """
        Check that only queued messages are sent.
        """
        kwargs = {
            'to': ['to@example.com'],
            'from_email': 'bob@example.com',
            'subject': 'Test',
            'message': 'Message',
        }
        failed_mail = Email.objects.create(status=STATUS.failed, **kwargs)
        none_mail = Email.objects.create(status=None, **kwargs)

        # This should be the only email that gets sent
        queued_mail = Email.objects.create(status=STATUS.queued, **kwargs)
        send_queued()
        self.assertNotEqual(Email.objects.get(id=failed_mail.id).status, STATUS.sent)
        self.assertNotEqual(Email.objects.get(id=none_mail.id).status, STATUS.sent)
        self.assertEqual(Email.objects.get(id=queued_mail.id).status, STATUS.sent)

    @override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
    def test_send_queued_mail_multi_processes(self):
        """
        Check that send_queued works well with multiple processes
        """
        kwargs = {
            'to': ['to@example.com'],
            'from_email': 'bob@example.com',
            'subject': 'Test',
            'message': 'Message',
            'status': STATUS.queued
        }

        # All three emails should be sent
        self.assertEqual(Email.objects.filter(status=STATUS.sent).count(), 0)
        for i in range(3):
            Email.objects.create(**kwargs)
        total_sent, total_failed = send_queued(processes=2)
        self.assertEqual(total_sent, 3)

    def test_send_bulk(self):
        """
        Ensure _send_bulk() properly sends out emails.
        """
        email = Email.objects.create(
            to=['to@example.com'], from_email='bob@example.com',
            subject='send bulk', message='Message', status=STATUS.queued,
            backend_alias='locmem')
        _send_bulk([email], uses_multiprocessing=False)
        self.assertEqual(Email.objects.get(id=email.id).status, STATUS.sent)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'send bulk')

    @override_settings(EMAIL_BACKEND='post_office.tests.test_mail.ConnectionTestingBackend')
    def test_send_bulk_reuses_open_connection(self):
        """
        Ensure _send_bulk() only opens connection once to send multiple emails.
        """
        global connection_counter
        self.assertEqual(connection_counter, 0)
        email = Email.objects.create(to=['to@example.com'],
                                     from_email='bob@example.com', subject='',
                                     message='', status=STATUS.queued, backend_alias='connection_tester')
        email_2 = Email.objects.create(to=['to@example.com'],
                                       from_email='bob@example.com', subject='',
                                       message='', status=STATUS.queued,
                                       backend_alias='connection_tester')
        _send_bulk([email, email_2])
        self.assertEqual(connection_counter, 1)

    def test_get_queued(self):
        """
        Ensure get_queued returns only emails that should be sent
        """
        kwargs = {
            'to': 'to@example.com',
            'from_email': 'bob@example.com',
            'subject': 'Test',
            'message': 'Message',
        }
        self.assertEqual(list(get_queued()), [])

        # Emails with statuses failed, sent or None shouldn't be returned
        Email.objects.create(status=STATUS.failed, **kwargs)
        Email.objects.create(status=None, **kwargs)
        Email.objects.create(status=STATUS.sent, **kwargs)
        self.assertEqual(list(get_queued()), [])

        # Email with queued status and None as scheduled_time should be included
        queued_email = Email.objects.create(status=STATUS.queued,
                                            scheduled_time=None, **kwargs)
        self.assertEqual(list(get_queued()), [queued_email])

        # Email scheduled for the future should not be included
        Email.objects.create(status=STATUS.queued,
                             scheduled_time=date(2020, 12, 13), **kwargs)
        self.assertEqual(list(get_queued()), [queued_email])

        # Email scheduled in the past should be included
        past_email = Email.objects.create(status=STATUS.queued,
                                          scheduled_time=date(2010, 12, 13), **kwargs)
        self.assertEqual(list(get_queued()), [queued_email, past_email])

    def test_get_batch_size(self):
        """
        Ensure BATCH_SIZE setting is read correctly.
        """
        previous_settings = settings.POST_OFFICE
        self.assertEqual(get_batch_size(), 100)
        setattr(settings, 'POST_OFFICE', {'BATCH_SIZE': 10})
        self.assertEqual(get_batch_size(), 10)
        settings.POST_OFFICE = previous_settings

    def test_get_threads_per_process(self):
        """
        Ensure THREADS_PER_PROCESS setting is read correctly.
        """
        previous_settings = settings.POST_OFFICE
        self.assertEqual(get_threads_per_process(), 5)
        setattr(settings, 'POST_OFFICE', {'THREADS_PER_PROCESS': 10})
        self.assertEqual(get_threads_per_process(), 10)
        settings.POST_OFFICE = previous_settings

    def test_get_log_level(self):
        """
        Ensure LOG_LEVEL setting is read correctly.
        """
        previous_settings = settings.POST_OFFICE
        self.assertEqual(get_log_level(), 2)
        setattr(settings, 'POST_OFFICE', {'LOG_LEVEL': 1})
        self.assertEqual(get_log_level(), 1)
        # Restore ``LOG_LEVEL``
        setattr(settings, 'POST_OFFICE', {'LOG_LEVEL': 2})
        settings.POST_OFFICE = previous_settings

    def test_create(self):
        """
        Test basic email creation
        """

        # Test that email is persisted only when commit=True
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            commit=False
        )
        self.assertEqual(email.id, None)
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            commit=True
        )
        self.assertNotEqual(email.id, None)

        # Test that email is created with the right status
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            priority=PRIORITY.now
        )
        self.assertEqual(email.status, None)
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            priority=PRIORITY.high
        )
        self.assertEqual(email.status, STATUS.queued)

        # Test that email is created with the right content
        context = {
            'subject': 'My subject',
            'message': 'My message',
            'html': 'My html',
        }
        now = datetime.now()
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            subject='Test {{ subject }}', message='Test {{ message }}',
            html_message='Test {{ html }}', context=context,
            scheduled_time=now, headers={'header': 'Test header'},
        )
        self.assertEqual(email.from_email, 'from@example.com')
        self.assertEqual(email.to, ['to@example.com'])
        self.assertEqual(email.subject, 'Test My subject')
        self.assertEqual(email.message, 'Test My message')
        self.assertEqual(email.html_message, 'Test My html')
        self.assertEqual(email.scheduled_time, now)
        self.assertEqual(email.headers, {'header': 'Test header'})

    def test_send_many(self):
        """Test send_many creates the right emails """
        kwargs_list = [
            {'sender': 'from@example.com', 'recipients': ['a@example.com']},
            {'sender': 'from@example.com', 'recipients': ['b@example.com']},
        ]
        send_many(kwargs_list)
        self.assertEqual(Email.objects.filter(to=['a@example.com']).count(), 1)

    def test_send_with_attachments(self):
        attachments = {
            'attachment_file1.txt': ContentFile('content'),
            'attachment_file2.txt': ContentFile('content'),
        }
        email = send(recipients=['a@example.com', 'b@example.com'],
                     sender='from@example.com', message='message',
                     subject='subject', attachments=attachments)

        self.assertTrue(email.pk)
        self.assertEqual(email.attachments.count(), 2)

    def test_send_with_render_on_delivery(self):
        """
        Ensure that mail.send() create email instances with appropriate
        fields being saved
        """
        template = EmailTemplate.objects.create(
            subject='Subject {{ name }}',
            content='Content {{ name }}',
            html_content='HTML {{ name }}'
        )
        context = {'name': 'test'}
        email = send(recipients=['a@example.com', 'b@example.com'],
                     template=template, context=context,
                     render_on_delivery=True)
        self.assertEqual(email.subject, '')
        self.assertEqual(email.message, '')
        self.assertEqual(email.html_message, '')
        self.assertEqual(email.template, template)

        # context shouldn't be persisted when render_on_delivery = False
        email = send(recipients=['a@example.com'],
                     template=template, context=context,
                     render_on_delivery=False)
        self.assertEqual(email.context, None)

    def test_send_with_attachments_multiple_recipients(self):
        """Test reusing the same attachment objects for several email objects"""
        attachments = {
            'attachment_file1.txt': ContentFile('content'),
            'attachment_file2.txt': ContentFile('content'),
        }
        email = send(recipients=['a@example.com', 'b@example.com'],
                     sender='from@example.com', message='message',
                     subject='subject', attachments=attachments)

        self.assertEqual(email.attachments.count(), 2)
        self.assertEqual(Attachment.objects.count(), 2)

    def test_create_with_template(self):
        """If render_on_delivery is True, subject and content
        won't be rendered, context also won't be saved."""

        template = EmailTemplate.objects.create(
            subject='Subject {{ name }}',
            content='Content {{ name }}',
            html_content='HTML {{ name }}'
        )
        context = {'name': 'test'}
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            template=template, context=context, render_on_delivery=True
        )
        self.assertEqual(email.subject, '')
        self.assertEqual(email.message, '')
        self.assertEqual(email.html_message, '')
        self.assertEqual(email.context, context)
        self.assertEqual(email.template, template)

    def test_create_with_template_and_empty_context(self):
        """If render_on_delivery is False, subject and content
        will be rendered, context won't be saved."""

        template = EmailTemplate.objects.create(
            subject='Subject {% now "Y" %}',
            content='Content {% now "Y" %}',
            html_content='HTML {% now "Y" %}'
        )
        context = None
        email = create(
            sender='from@example.com', recipients=['to@example.com'],
            template=template, context=context
        )
        today = date.today()
        current_year = today.year
        self.assertEqual(email.subject, 'Subject %d' % current_year)
        self.assertEqual(email.message, 'Content %d' % current_year)
        self.assertEqual(email.html_message, 'HTML %d' % current_year)
        self.assertEqual(email.context, None)
        self.assertEqual(email.template, None)

    def test_backend_alias(self):
        """Test backend_alias field is properly set."""

        email = send(recipients=['a@example.com'],
                     sender='from@example.com', message='message',
                     subject='subject')
        self.assertEqual(email.backend_alias, '')

        email = send(recipients=['a@example.com'],
                     sender='from@example.com', message='message',
                     subject='subject', backend='locmem')
        self.assertEqual(email.backend_alias, 'locmem')

        with self.assertRaises(ValueError):
            send(recipients=['a@example.com'], sender='from@example.com',
                 message='message', subject='subject', backend='foo')

    @override_settings(LANGUAGES=(('en', 'English'), ('ru', 'Russian')))
    def test_send_with_template(self):
        """If render_on_delivery is False, subject and content
        will be rendered, context won't be saved."""

        template = EmailTemplate.objects.create(
            subject='Subject {{ name }}',
            content='Content {{ name }}',
            html_content='HTML {{ name }}'
        )
        russian_template = EmailTemplate(
            default_template=template,
            language='ru',
            subject='предмет {{ name }}',
            content='содержание {{ name }}',
            html_content='HTML {{ name }}'
        )
        russian_template.save()

        context = {'name': 'test'}
        email = send(recipients=['to@example.com'], sender='from@example.com',
                     template=template, context=context)
        email = Email.objects.get(id=email.id)
        self.assertEqual(email.subject, 'Subject test')
        self.assertEqual(email.message, 'Content test')
        self.assertEqual(email.html_message, 'HTML test')
        self.assertEqual(email.context, None)
        self.assertEqual(email.template, None)

        # check, if we use the Russian version
        email = send(recipients=['to@example.com'], sender='from@example.com',
                     template=russian_template, context=context)
        email = Email.objects.get(id=email.id)
        self.assertEqual(email.subject, 'предмет test')
        self.assertEqual(email.message, 'содержание test')
        self.assertEqual(email.html_message, 'HTML test')
        self.assertEqual(email.context, None)
        self.assertEqual(email.template, None)

        # Check that send picks template with the right language
        email = send(recipients=['to@example.com'], sender='from@example.com',
                     template=template, context=context, language='ru')
        email = Email.objects.get(id=email.id)
        self.assertEqual(email.subject, 'предмет test')

        email = send(recipients=['to@example.com'], sender='from@example.com',
                     template=template, context=context, language='ru',
                     render_on_delivery=True)
        self.assertEqual(email.template.language, 'ru')

    def test_send_bulk_with_faulty_template(self):
        template = EmailTemplate.objects.create(
            subject='{% if foo %}Subject {{ name }}',
            content='Content {{ name }}',
            html_content='HTML {{ name }}'
        )
        email = Email.objects.create(to='to@example.com', from_email='from@example.com',
                                     template=template, status=STATUS.queued)
        _send_bulk([email], uses_multiprocessing=False)
        email = Email.objects.get(id=email.id)
        self.assertEqual(email.status, STATUS.failed)