# coding: utf-8
from __future__ import unicode_literals

import sys
from contextlib import contextmanager

import pytest
from django import VERSION
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.core.mail import EmailMultiAlternatives, send_mail, send_mass_mail
from requests import ConnectTimeout, Response

from postmarker.core import TEST_TOKEN
from postmarker.django import EmailBackend
from postmarker.django.signals import on_exception, post_send, pre_send
from postmarker.exceptions import PostmarkerException
from postmarker.models.emails import Email

from .._compat import patch


pytestmark = pytest.mark.usefixtures("outbox")


SEND_KWARGS = {
    "subject": "Subject here",
    "message": "Here is the message.",
    "from_email": "sender@example.com",
    "recipient_list": ["receiver@example.com"],
}


def send_with_connection(connection):
    mail.EmailMessage("Subject", "Body", "sender@example.com", ["receiver@example.com"], connection=connection).send()


def test_send_mail(postmark_request, settings):
    send_mail(**SEND_KWARGS)
    assert postmark_request.call_args[1]["json"] == (
        {
            "ReplyTo": None,
            "Subject": "Subject here",
            "To": "receiver@example.com",
            "Bcc": None,
            "Headers": [],
            "Cc": None,
            "Attachments": [],
            "TextBody": "Here is the message.",
            "HtmlBody": None,
            "Tag": None,
            "Metadata": None,
            "TrackOpens": False,
            "From": "sender@example.com",
        },
    )
    assert postmark_request.call_args[1]["headers"]["X-Postmark-Server-Token"] == settings.POSTMARK["TOKEN"]


@pytest.mark.parametrize(
    "kwarg, key",
    (
        ("bcc", "Bcc"),
        ("cc", "Cc"),
        pytest.mark.skipif(VERSION[:2] < (1, 7), reason="Django < 1.7 doesn't support `reply_to`")(
            ("reply_to", "ReplyTo")
        ),
    ),
)
def test_reply_to_cc_bcc(postmark_request, kwarg, key):
    message = mail.EmailMessage(
        "Subject",
        "Body",
        "sender@example.com",
        ["receiver@example.com"],
        **{kwarg: ["r1@example.com", "r2@example.com"]}
    )
    message.send()
    assert postmark_request.call_args[1]["json"][0][key] == "r1@example.com, r2@example.com"


EXAMPLE_BATCH_RESPONSE = [
    {
        "ErrorCode": 0,
        "Message": "OK",
        "MessageID": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817",
        "SubmittedAt": "2010-11-26T12:01:05.1794748-05:00",
        "To": "receiver@example.com",
    },
    {
        "ErrorCode": 406,
        "Message": "Bla bla, inactive recipient",
        "MessageID": "e2ecbbfc-fe12-463d-b933-9fe22915106d",
        "SubmittedAt": "2010-11-26T12:01:05.1794748-05:00",
        "To": "invalid@example.com",
    },
]


class TestMassSend:
    messages = [
        ("Subject", "Body", "sender@example.com", ["receiver@example.com"]),
        ("Subject", "Body", "sender@example.com", ["invalid@example.com"]),
    ]

    @pytest.fixture
    def batch_send(self):
        @contextmanager
        def manager(return_value=EXAMPLE_BATCH_RESPONSE):
            with patch("postmarker.models.emails.EmailBatch.send", return_value=return_value) as send:
                yield send

        return manager

    def test_send_mass(self, batch_send):
        with batch_send() as send:
            assert send_mass_mail([]) is None
            assert not send.called

    def test_sent_messages_count(self, batch_send):
        with batch_send():
            assert send_mass_mail(self.messages, fail_silently=True) == 1

    def test_single_exception_propagation(self, batch_send):
        with batch_send():
            with pytest.raises(PostmarkerException) as exc:
                send_mass_mail(self.messages, fail_silently=False)
            assert str(exc.value) == "[406] Bla bla, inactive recipient"

    def test_multiple_exceptions_propagation(self, batch_send):
        with batch_send(EXAMPLE_BATCH_RESPONSE * 2):
            with pytest.raises(PostmarkerException) as exc:
                send_mass_mail(self.messages * 2, fail_silently=False)
            assert str(exc.value) == "[[406] Bla bla, inactive recipient, [406] Bla bla, inactive recipient]"


@pytest.fixture
def message():
    return EmailMultiAlternatives(
        subject="subject", body="text_content", from_email="sender@example.com", to=["receiver@example.com"]
    )


def test_send_mail_with_attachment(postmark_request, message):
    """
    Test sending email with attachment

    The Django backend crashes when sending an email with an attachment.
    https://github.com/Stranger6667/postmarker/issues/98

    This test does not send any mail. Instead, it demonstrates the bug by
    constructing the same underlying datastructures that the backend would
    construct.
    """
    message.attach("hello.txt", "Hello World", "text/plain")
    message.send()
    assert postmark_request.call_args[1]["json"][0] == {
        "TextBody": "text_content",
        "Attachments": [{"Name": "hello.txt", "Content": "Hello World", "ContentType": "text/plain"}],
        "From": "sender@example.com",
        "HtmlBody": None,
        "ReplyTo": None,
        "Subject": "subject",
        "To": "receiver@example.com",
        "Headers": [],
        "TrackOpens": False,
        "Cc": None,
        "Bcc": None,
        "Tag": None,
        "Metadata": None,
    }


def test_text_html_alternative_and_pdf_attachment_failure(postmark_request, message):
    """
    Send a text body, HTML alternative, and PDF attachment.
    """
    message.attach_alternative("<html></html>", "text/html")
    if sys.version_info[:2] == (3, 2):
        content = b"PDF-File-Contents"
    else:
        content = "PDF-File-Contents"
    message.attach("hello.pdf", content, "application/pdf")
    message.send(fail_silently=False)
    if sys.version_info[0] < 3:
        encoded_content = "UERGLUZpbGUtQ29udGVudHM="
    else:
        encoded_content = "UERGLUZpbGUtQ29udGVudHM=\n"
    assert postmark_request.call_args[1]["json"][0] == {
        "Attachments": [{"Content": encoded_content, "ContentType": "application/pdf", "Name": "hello.pdf"}],
        "Bcc": None,
        "Cc": None,
        "From": "sender@example.com",
        "Headers": [],
        "HtmlBody": "<html></html>",
        "ReplyTo": None,
        "Subject": "subject",
        "Tag": None,
        "Metadata": None,
        "TextBody": "text_content",
        "To": "receiver@example.com",
        "TrackOpens": False,
    }


def test_message_rfc822(postmark_request, message):
    if sys.version_info[:2] == (3, 2):
        message_content = b"Fake message"
        expected_content = "RmFrZSBtZXNzYWdl\n"
    else:
        message_content = "Fake message"
        expected_content = "RmFrZSBtZXNzYWdl"
    message.attach_alternative(message_content, "message/rfc822")
    message.send(fail_silently=False)
    assert postmark_request.call_args[1]["json"][0] == {
        "Attachments": [{"Name": "attachment.txt", "Content": expected_content, "ContentType": "message/rfc822"}],
        "Bcc": None,
        "Cc": None,
        "From": "sender@example.com",
        "Headers": [],
        "HtmlBody": None,
        "ReplyTo": None,
        "Subject": "subject",
        "Tag": None,
        "Metadata": None,
        "TextBody": "text_content",
        "To": "receiver@example.com",
        "TrackOpens": False,
    }


def test_headers_encoding(postmark_request):
    kwargs = {
        "subject": "Тест",
        "message": "Here is the message.",
        "from_email": "Тест <sender@example.com>",
        "recipient_list": ["Тест <receiver@example.com>", "Тест2 <receiver@example.com>"],
    }
    send_mail(**kwargs)
    request = postmark_request.call_args[1]["json"][0]
    assert request["Subject"] == kwargs["subject"]
    assert request["From"] == kwargs["from_email"]
    assert request["To"] == ", ".join(kwargs["recipient_list"])


@pytest.mark.skipif(
    VERSION[:2] < (1, 7), reason="Django < 1.7 does not support `html_message` argument in `send_mail` function."
)
@pytest.mark.parametrize(
    "html_message",
    (
        "<html></html>",
        "<html>Тест</html>",
        """<html>
       <body>
           <div>
             %s
           </div>
       </body>
       </html>"""
        % ("." * 1000),
    ),
)
def test_send_mail_html_message(html_message, postmark_request):
    send_mail(html_message=html_message, **SEND_KWARGS)
    assert postmark_request.call_args[1]["json"] == (
        {
            "ReplyTo": None,
            "Subject": "Subject here",
            "To": "receiver@example.com",
            "Bcc": None,
            "Headers": [],
            "Cc": None,
            "Attachments": [],
            "TextBody": "Here is the message.",
            "HtmlBody": html_message,
            "TrackOpens": False,
            "Tag": None,
            "Metadata": None,
            "From": "sender@example.com",
        },
    )


def test_send_long_text_line(postmark_request):
    kwargs = SEND_KWARGS.copy()
    message = "A" * 1000
    kwargs["message"] = message
    send_mail(**kwargs)
    assert postmark_request.call_args[1]["json"] == (
        {
            "ReplyTo": None,
            "Subject": "Subject here",
            "To": "receiver@example.com",
            "Bcc": None,
            "Headers": [],
            "Cc": None,
            "Attachments": [],
            "TextBody": message,
            "HtmlBody": None,
            "TrackOpens": False,
            "Tag": None,
            "Metadata": None,
            "From": "sender@example.com",
        },
    )


def test_missing_api_key(settings):
    settings.POSTMARK = {}
    with pytest.raises(ImproperlyConfigured) as exc:
        send_mail(**SEND_KWARGS)
    assert str(exc.value) == "You should specify TOKEN to use Postmark email backend"


def test_test_mode(settings, postmark_request):
    settings.POSTMARK = {"TEST_MODE": True}
    send_mail(**SEND_KWARGS)
    assert postmark_request.call_args[1]["headers"]["X-Postmark-Server-Token"] == TEST_TOKEN


def test_extra_options(settings, postmark_request):
    settings.POSTMARK["TRACK_OPENS"] = True
    send_mail(**SEND_KWARGS)
    assert postmark_request.call_args[1]["json"] == (
        {
            "ReplyTo": None,
            "Subject": "Subject here",
            "To": "receiver@example.com",
            "Bcc": None,
            "Headers": [],
            "Cc": None,
            "Attachments": [],
            "TextBody": "Here is the message.",
            "HtmlBody": None,
            "Tag": None,
            "Metadata": None,
            "TrackOpens": True,
            "From": "sender@example.com",
        },
    )


@pytest.mark.skipif(VERSION[:2] < (1, 8), reason="Context manager protocol was added in Django 1.8")
def test_context_manager(postmark_request):
    with mail.get_connection() as connection:
        send_with_connection(connection)
    assert postmark_request.call_args[1]["json"] == (
        {
            "ReplyTo": None,
            "Subject": "Subject",
            "To": "receiver@example.com",
            "Bcc": None,
            "Headers": [],
            "Cc": None,
            "Attachments": [],
            "TextBody": "Body",
            "HtmlBody": None,
            "Tag": None,
            "Metadata": None,
            "TrackOpens": True,
            "From": "sender@example.com",
        },
    )


@pytest.mark.skipif(VERSION[:2] < (1, 8), reason="Context manager protocol was added in Django 1.8")
class TestExceptions:
    @pytest.fixture(autouse=True)
    def setup(self, postmark_request):
        postmark_request.return_value.json.side_effect = ValueError

    def test_silent_exception(self):
        with mail.get_connection(fail_silently=True) as connection:
            send_with_connection(connection)

    def test_loud_exception(self):
        with mail.get_connection() as connection:
            with pytest.raises(ValueError):
                send_with_connection(connection)


@pytest.mark.skipif(VERSION[:2] < (1, 8), reason="Context manager protocol was added in Django 1.8")
def test_close_closed_connection():
    with mail.get_connection() as connection:
        connection.close()


@pytest.mark.usefixtures("postmark_request")
class TestSignals:
    def test_pre_send(self, catch_signal):
        with catch_signal(pre_send) as handler:
            send_mail(**SEND_KWARGS)
        assert handler.called
        kwargs = handler.call_args[1]
        assert kwargs["sender"] == EmailBackend
        assert kwargs["signal"] == pre_send
        assert len(kwargs["messages"]) == 1
        message = kwargs["messages"][0]
        assert Email.from_mime(message, None).as_dict() == {
            "Attachments": [],
            "Bcc": None,
            "Cc": None,
            "From": "sender@example.com",
            "Headers": [],
            "HtmlBody": None,
            "ReplyTo": None,
            "Subject": "Subject here",
            "Tag": None,
            "Metadata": None,
            "TextBody": "Here is the message.",
            "To": "receiver@example.com",
        }

    def test_post_send(self, catch_signal, postmark_request):
        postmark_request.return_value = Response()
        postmark_request.return_value.status_code = 200
        postmark_request.return_value._content = (
            b'[{"ErrorCode": 0, "To": "receiver@example.com", '
            b'"SubmittedAt": "2016-10-06T10:05:30.570118-04:00", '
            b'"Message": "Test job accepted", '
            b'"MessageID": "96a981da-9b7c-4aa9-bda2-84ab99097686"}]'
        )
        with catch_signal(post_send) as handler:
            send_mail(**SEND_KWARGS)
        assert handler.called
        kwargs = handler.call_args[1]
        assert kwargs["sender"] == EmailBackend
        assert kwargs["signal"] == post_send
        assert kwargs["response"] == [
            {
                "ErrorCode": 0,
                "Message": "Test job accepted",
                "MessageID": "96a981da-9b7c-4aa9-bda2-84ab99097686",
                "SubmittedAt": "2016-10-06T10:05:30.570118-04:00",
                "To": "receiver@example.com",
            }
        ]

    def test_on_exception(self, catch_signal):
        with patch("requests.Session.request", side_effect=ConnectTimeout):
            with catch_signal(on_exception) as handler:
                send_mail(fail_silently=True, **SEND_KWARGS)
        assert handler.called
        kwargs = handler.call_args[1]
        assert kwargs["sender"] == EmailBackend
        assert kwargs["signal"] == on_exception
        assert isinstance(kwargs["exception"], ConnectTimeout)
        assert len(kwargs["raw_messages"]) == 1