import functools
from unittest.mock import patch

from django.core import mail
from django.core.mail.message import EmailMultiAlternatives

from sentry import options
from sentry.models import GroupEmailThread, User, UserEmail, UserOption
from sentry.testutils import TestCase
from sentry.utils import json
from sentry.utils.email import MessageBuilder
from sentry.utils.email.faker import create_fake_email


class MessageBuilderTest(TestCase):
    def test_raw_content(self):
        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
            headers={"X-Test": "foo"},
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test"
        assert out.extra_headers["X-Test"] == "foo"
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

    def test_inline_css(self):
        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<head><style type='text/css'>h1 { color: red; }</style></head><h1>foobar</h1><h2><b>hello world</b></h2>",
            headers={"X-Test": "foo"},
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test"
        assert out.extra_headers["X-Test"] == "foo"
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            '<!DOCTYPE html>\n<html><head></head><body><h1 style="color: red">foobar</h1><h2><b>hello world</b></h2></body></html>',
            "text/html",
        )

    def test_explicit_reply_to(self):
        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
            headers={"X-Sentry-Reply-To": "bar@example.com"},
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test"
        assert out.extra_headers["Reply-To"] == "bar@example.com"
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

    def test_with_users(self):
        project = self.project

        user_a = User.objects.create(email="foo@example.com")
        user_b = User.objects.create(email="bar@example.com")
        user_c = User.objects.create(email="baz@example.com")

        alternate_email = "bazzer@example.com"
        UserEmail.objects.create(user=user_c, email=alternate_email)
        UserOption.objects.create(
            user=user_c, project_id=project.id, key="mail:email", value=alternate_email
        )

        msg = MessageBuilder(
            subject="Test", body="hello world", html_body="<!DOCTYPE html>\n<b>hello world</b>"
        )
        msg.add_users([user_a.id, user_b.id, user_c.id], project=project)
        msg.send()

        assert len(mail.outbox) == 3

        assert sorted(out.to[0] for out in mail.outbox) == [
            "bar@example.com",
            "bazzer@example.com",
            "foo@example.com",
        ]

    def test_fake_dont_send(self):
        project = self.project

        user_a = User.objects.create(email=create_fake_email("foo", "fake"))
        user_b = User.objects.create(email=create_fake_email("bar", "fake"))
        user_c = User.objects.create(email=create_fake_email("baz", "fake"))

        UserOption.objects.create(
            user=user_c,
            project_id=project.id,
            key="mail:email",
            value=create_fake_email("bazzer", "fake"),
        )

        msg = MessageBuilder(
            subject="Test", body="hello world", html_body="<!DOCTYPE html>\n<b>hello world</b>"
        )
        msg.add_users([user_a.id, user_b.id, user_c.id], project=project)
        msg.send()

        assert len(mail.outbox) == 0

    @patch("sentry.utils.email.message_builder.make_msgid")
    def test_message_id(self, make_msgid):
        make_msgid.return_value = "abc123"

        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test"
        assert out.extra_headers["Message-Id"] == "abc123"
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

    @patch("sentry.utils.email.message_builder.make_msgid")
    def test_add_groupemailthread(self, make_msgid):
        make_msgid.return_value = "abc123"

        msg = MessageBuilder(
            subject="Test", body="hello world", html_body="<b>hello world</b>", reference=self.group
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test", "First message should not have Re: prefix"
        assert out.extra_headers["Message-Id"] == "abc123"
        assert "In-Reply-To" not in out.extra_headers
        assert "References" not in out.extra_headers
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

        # Our new EmailThread row was added
        assert GroupEmailThread.objects.count() == 1
        thread = GroupEmailThread.objects.all()[0]
        assert thread.msgid == "abc123"
        assert thread.email == "foo@example.com"
        assert thread.group == self.group

    @patch("sentry.utils.email.message_builder.make_msgid")
    def test_reply_reference(self, make_msgid):
        make_msgid.return_value = "abc123"

        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
            reference=self.activity,
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Re: Test"
        assert out.extra_headers["Message-Id"] == "abc123"
        assert "In-Reply-To" not in out.extra_headers
        assert "References" not in out.extra_headers
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

        # Our new EmailThread row was added
        assert GroupEmailThread.objects.count() == 1
        thread = GroupEmailThread.objects.all()[0]
        assert thread.msgid == "abc123"
        assert thread.email == "foo@example.com"
        assert thread.group == self.group

        # new msgid for the next message
        make_msgid.return_value = "321cba"
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 2

        out = mail.outbox[1]
        assert isinstance(out, EmailMultiAlternatives)
        assert out.to == ["foo@example.com"]
        assert out.subject == "Re: Test"
        assert out.extra_headers["Message-Id"] == "321cba"
        assert out.extra_headers["In-Reply-To"] == "abc123"
        assert out.extra_headers["References"] == "abc123"
        assert out.body == "hello world"
        assert len(out.alternatives) == 1
        assert out.alternatives[0] == (
            "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>",
            "text/html",
        )

        # Our new GroupEmailThread row was added
        assert GroupEmailThread.objects.count() == 1, "Should not have added a new row"
        assert GroupEmailThread.objects.all()[0].msgid == "abc123", "msgid should not have changed"

    def test_get_built_messages(self):
        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
            reference=self.activity,
        )
        results = msg.get_built_messages(["foo@example.com"])
        assert len(results) == 1

    def test_bcc_on_send(self):
        msg = MessageBuilder(subject="Test", body="hello world")
        msg.send(["foo@example.com"], bcc=["bar@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert out.to == ["foo@example.com"]
        assert out.bcc == ["bar@example.com"]

    def test_generates_list_ids_for_registered_types(self):
        build_message = functools.partial(
            MessageBuilder, subject="Test", body="hello world", html_body="<b>hello world</b>"
        )

        expected = "<{event.project.slug}.{event.organization.slug}.{namespace}>".format(
            event=self.event, namespace=options.get("mail.list-namespace")
        )

        references = (self.event.group, self.event.project, self.activity)

        for reference in references:
            (message,) = build_message(reference=reference).get_built_messages(["foo@example.com"])
            assert message.message()["List-Id"] == expected

    def test_does_not_generates_list_ids_for_unregistered_types(self):
        message = (
            MessageBuilder(
                subject="Test",
                body="hello world",
                html_body="<b>hello world</b>",
                reference=self.user,
            )
            .get_built_messages(["foo@example.com"])[0]
            .message()
        )

        assert "List-Id" not in message

    def test_stripped_newline(self):
        msg = MessageBuilder(
            subject="Foo\r\nBar", body="hello world", html_body="<b>hello world</b"
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1
        assert mail.outbox[0].subject == "Foo"

    def test_adds_type_to_headers(self):
        msg = MessageBuilder(
            subject="Test",
            body="hello world",
            html_body="<b>hello world</b>",
            headers={"X-Test": "foo"},
            type="test_email.type",
        )
        msg.send(["foo@example.com"])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert out.to == ["foo@example.com"]
        assert out.subject == "Test"
        assert out.extra_headers["X-Test"] == "foo"

        json_xsmtpapi_data = json.loads(out.extra_headers["X-SMTPAPI"])
        assert json_xsmtpapi_data["category"] == "test_email.type"
