# -*- coding: utf-8; -*-

from unittest import TestCase
from unittest.mock import patch, MagicMock

import pytest

from wuttjamaican.email import handler as mod
from wuttjamaican.email import Message
from wuttjamaican.conf import WuttaConfig
from wuttjamaican.util import resource_path
from wuttjamaican.exc import ConfigurationError


class TestEmailHandler(TestCase):

    def setUp(self):
        try:
            import mako
        except ImportError:
            pytest.skip("test not relevant without mako")

        self.config = WuttaConfig()
        self.app = self.config.get_app()

    def make_handler(self, **kwargs):
        return mod.EmailHandler(self.config, **kwargs)

    def test_constructor_lookups(self):

        # empty lookup paths by default, if no providers
        with patch.object(self.app, 'providers', new={}):
            handler = self.make_handler()
        self.assertEqual(handler.txt_templates.directories, [])
        self.assertEqual(handler.html_templates.directories, [])

        # provider may specify paths as list
        providers = {
            'wuttatest': MagicMock(email_templates=['wuttjamaican.email:templates']),
        }
        with patch.object(self.app, 'providers', new=providers):
            handler = self.make_handler()
        path = resource_path('wuttjamaican.email:templates')
        self.assertEqual(handler.txt_templates.directories, [path])
        self.assertEqual(handler.html_templates.directories, [path])

        # provider may specify paths as string
        providers = {
            'wuttatest': MagicMock(email_templates='wuttjamaican.email:templates'),
        }
        with patch.object(self.app, 'providers', new=providers):
            handler = self.make_handler()
        path = resource_path('wuttjamaican.email:templates')
        self.assertEqual(handler.txt_templates.directories, [path])
        self.assertEqual(handler.html_templates.directories, [path])

    def test_make_message(self):
        handler = self.make_handler()
        msg = handler.make_message()
        self.assertIsInstance(msg, Message)

    def test_make_auto_message(self):
        handler = self.make_handler()

        # error if default sender not defined
        self.assertRaises(ConfigurationError, handler.make_auto_message, 'foo')

        # so let's define that
        self.config.setdefault('wutta.email.default.sender', 'bob@example.com')

        # message is empty by default
        msg = handler.make_auto_message('foo')
        self.assertIsInstance(msg, Message)
        self.assertEqual(msg.key, 'foo')
        self.assertEqual(msg.sender, 'bob@example.com')
        self.assertEqual(msg.subject, "Automated message")
        self.assertEqual(msg.to, [])
        self.assertEqual(msg.cc, [])
        self.assertEqual(msg.bcc, [])
        self.assertIsNone(msg.replyto)
        self.assertIsNone(msg.txt_body)
        self.assertIsNone(msg.html_body)

        # but if there is a proper email profile configured for key,
        # then we should get back a more complete message
        self.config.setdefault('wutta.email.test_foo.subject', "hello foo")
        self.config.setdefault('wutta.email.test_foo.to', 'sally@example.com')
        self.config.setdefault('wutta.email.templates', 'tests.email:templates')
        handler = self.make_handler()
        msg = handler.make_auto_message('test_foo')
        self.assertEqual(msg.key, 'test_foo')
        self.assertEqual(msg.sender, 'bob@example.com')
        self.assertEqual(msg.subject, "hello foo")
        self.assertEqual(msg.to, ['sally@example.com'])
        self.assertEqual(msg.cc, [])
        self.assertEqual(msg.bcc, [])
        self.assertIsNone(msg.replyto)
        self.assertEqual(msg.txt_body, "hello from foo txt template\n")
        self.assertEqual(msg.html_body, "<p>hello from foo html template</p>\n")

        # *some* auto methods get skipped if caller specifies the
        # kwarg at all; others get skipped if kwarg is empty

        # sender
        with patch.object(handler, 'get_auto_sender') as get_auto_sender:
            msg = handler.make_auto_message('foo', sender=None)
            get_auto_sender.assert_not_called()
            msg = handler.make_auto_message('foo')
            get_auto_sender.assert_called_once_with('foo')

        # subject
        with patch.object(handler, 'get_auto_subject') as get_auto_subject:
            msg = handler.make_auto_message('foo', subject=None)
            get_auto_subject.assert_not_called()
            msg = handler.make_auto_message('foo')
            get_auto_subject.assert_called_once_with('foo', {})

        # to
        with patch.object(handler, 'get_auto_to') as get_auto_to:
            msg = handler.make_auto_message('foo', to=None)
            get_auto_to.assert_not_called()
            get_auto_to.return_value = None
            msg = handler.make_auto_message('foo')
            get_auto_to.assert_called_once_with('foo')

        # cc
        with patch.object(handler, 'get_auto_cc') as get_auto_cc:
            msg = handler.make_auto_message('foo', cc=None)
            get_auto_cc.assert_not_called()
            get_auto_cc.return_value = None
            msg = handler.make_auto_message('foo')
            get_auto_cc.assert_called_once_with('foo')

        # bcc
        with patch.object(handler, 'get_auto_bcc') as get_auto_bcc:
            msg = handler.make_auto_message('foo', bcc=None)
            get_auto_bcc.assert_not_called()
            get_auto_bcc.return_value = None
            msg = handler.make_auto_message('foo')
            get_auto_bcc.assert_called_once_with('foo')

        # txt_body
        with patch.object(handler, 'get_auto_txt_body') as get_auto_txt_body:
            msg = handler.make_auto_message('foo', txt_body=None)
            get_auto_txt_body.assert_not_called()
            msg = handler.make_auto_message('foo')
            get_auto_txt_body.assert_called_once_with('foo', {})

        # html_body
        with patch.object(handler, 'get_auto_html_body') as get_auto_html_body:
            msg = handler.make_auto_message('foo', html_body=None)
            get_auto_html_body.assert_not_called()
            msg = handler.make_auto_message('foo')
            get_auto_html_body.assert_called_once_with('foo', {})

    def test_get_auto_sender(self):
        handler = self.make_handler()

        # error if none configured
        self.assertRaises(ConfigurationError, handler.get_auto_sender, 'foo')

        # can set global default
        self.config.setdefault('wutta.email.default.sender', 'bob@example.com')
        self.assertEqual(handler.get_auto_sender('foo'), 'bob@example.com')

        # can set for key
        self.config.setdefault('wutta.email.foo.sender', 'sally@example.com')
        self.assertEqual(handler.get_auto_sender('foo'), 'sally@example.com')

    def test_get_auto_subject_template(self):
        handler = self.make_handler()

        # global default
        template = handler.get_auto_subject_template('foo')
        self.assertEqual(template, "Automated message")

        # can configure alternate global default
        self.config.setdefault('wutta.email.default.subject', "Wutta Message")
        template = handler.get_auto_subject_template('foo')
        self.assertEqual(template, "Wutta Message")

        # can configure just for key
        self.config.setdefault('wutta.email.foo.subject', "Foo Message")
        template = handler.get_auto_subject_template('foo')
        self.assertEqual(template, "Foo Message")

    def test_get_auto_subject(self):
        handler = self.make_handler()

        # global default
        subject = handler.get_auto_subject('foo')
        self.assertEqual(subject, "Automated message")

        # can configure alternate global default
        self.config.setdefault('wutta.email.default.subject', "Wutta Message")
        subject = handler.get_auto_subject('foo')
        self.assertEqual(subject, "Wutta Message")

        # can configure just for key
        self.config.setdefault('wutta.email.foo.subject', "Foo Message")
        subject = handler.get_auto_subject('foo')
        self.assertEqual(subject, "Foo Message")

        # proper template is rendered
        self.config.setdefault('wutta.email.bar.subject', "${foo} Message")
        subject = handler.get_auto_subject('bar', {'foo': "FOO"})
        self.assertEqual(subject, "FOO Message")

        # unless we ask it not to
        subject = handler.get_auto_subject('bar', {'foo': "FOO"}, rendered=False)
        self.assertEqual(subject, "${foo} Message")

    def test_get_auto_recips(self):
        handler = self.make_handler()

        # error if bad type requested
        self.assertRaises(ValueError, handler.get_auto_recips, 'foo', 'doesnotexist')

        # can configure global default
        self.config.setdefault('wutta.email.default.to', 'admin@example.com')
        recips = handler.get_auto_recips('foo', 'to')
        self.assertEqual(recips, ['admin@example.com'])

        # can configure just for key
        self.config.setdefault('wutta.email.foo.to', 'bob@example.com')
        recips = handler.get_auto_recips('foo', 'to')
        self.assertEqual(recips, ['bob@example.com'])

    def test_get_auto_body_template(self):
        from mako.template import Template

        handler = self.make_handler()

        # error if bad request
        self.assertRaises(ValueError, handler.get_auto_body_template, 'foo', 'BADTYPE')

        # empty by default
        template = handler.get_auto_body_template('foo', 'txt')
        self.assertIsNone(template)

        # but returns a template if it exists
        providers = {
            'wuttatest': MagicMock(email_templates=['tests.email:templates']),
        }
        with patch.object(self.app, 'providers', new=providers):
            handler = self.make_handler()
        template = handler.get_auto_body_template('test_foo', 'txt')
        self.assertIsInstance(template, Template)
        self.assertEqual(template.uri, 'test_foo.txt.mako')

    def test_get_auto_txt_body(self):
        handler = self.make_handler()

        # empty by default
        body = handler.get_auto_txt_body('some-random-email')
        self.assertIsNone(body)

        # but returns body if template exists
        providers = {
            'wuttatest': MagicMock(email_templates=['tests.email:templates']),
        }
        with patch.object(self.app, 'providers', new=providers):
            handler = self.make_handler()
        body = handler.get_auto_txt_body('test_foo')
        self.assertEqual(body, 'hello from foo txt template\n')

    def test_get_auto_html_body(self):
        handler = self.make_handler()

        # empty by default
        body = handler.get_auto_html_body('some-random-email')
        self.assertIsNone(body)

        # but returns body if template exists
        providers = {
            'wuttatest': MagicMock(email_templates=['tests.email:templates']),
        }
        with patch.object(self.app, 'providers', new=providers):
            handler = self.make_handler()
        body = handler.get_auto_html_body('test_foo')
        self.assertEqual(body, '<p>hello from foo html template</p>\n')

    def test_deliver_message(self):
        handler = self.make_handler()

        msg = handler.make_message(sender='bob@example.com', to='sally@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):

            # no smtp session since sending email is disabled by default
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_not_called()
                session.login.assert_not_called()
                session.sendmail.assert_not_called()

            # now let's enable sending
            self.config.setdefault('wutta.mail.send_emails', 'true')

            # smtp login not attempted by default
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_called_once_with('localhost')
                session.login.assert_not_called()
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

            # but login attempted if config has credentials
            self.config.setdefault('wutta.mail.smtp.username', 'bob')
            self.config.setdefault('wutta.mail.smtp.password', 'seekrit')
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_called_once_with('localhost')
                session.login.assert_called_once_with('bob', 'seekrit')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

        # error if no sender
        msg = handler.make_message(to='sally@example.com')
        self.assertRaises(ValueError, handler.deliver_message, msg)

        # error if no recips
        msg = handler.make_message(sender='bob@example.com')
        self.assertRaises(ValueError, handler.deliver_message, msg)

        # can set recips as list
        msg = handler.make_message(sender='bob@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg, recips=['sally@example.com'])
                smtplib.SMTP.assert_called_once_with('localhost')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

        # can set recips as string
        msg = handler.make_message(sender='bob@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg, recips='sally@example.com')
                smtplib.SMTP.assert_called_once_with('localhost')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

        # can set recips via to
        msg = handler.make_message(sender='bob@example.com', to='sally@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_called_once_with('localhost')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

        # can set recips via cc
        msg = handler.make_message(sender='bob@example.com', cc='sally@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_called_once_with('localhost')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

        # can set recips via bcc
        msg = handler.make_message(sender='bob@example.com', bcc='sally@example.com')
        with patch.object(msg, 'as_string', return_value='msg-str'):
            with patch.object(mod, 'smtplib') as smtplib:
                session = MagicMock()
                smtplib.SMTP.return_value = session
                handler.deliver_message(msg)
                smtplib.SMTP.assert_called_once_with('localhost')
                session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')

    def test_sending_is_enabled(self):
        handler = self.make_handler()

        # off by default
        self.assertFalse(handler.sending_is_enabled())

        # but can be turned on
        self.config.setdefault('wutta.mail.send_emails', 'true')
        self.assertTrue(handler.sending_is_enabled())

    def test_send_email(self):
        with patch.object(mod.EmailHandler, 'deliver_message') as deliver_message:
            handler = self.make_handler()

            # deliver_message() is called
            handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
                               txt_body='hello world')
            deliver_message.assert_called_once()

            # make_auto_message() called only if needed
            with patch.object(handler, 'make_auto_message') as make_auto_message:

                msg = handler.make_message()
                handler.send_email(message=msg)
                make_auto_message.assert_not_called()

                handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
                                   txt_body='hello world')
                make_auto_message.assert_called_once_with('foo', {},
                                                          sender='bob@example.com',
                                                          to='sally@example.com',
                                                          txt_body='hello world')