from contextlib import contextmanager
from email.message import Message
from typing import Dict, Generator

from smtplib import SMTP, SMTP_SSL

from .._model import TransportEncryption
from .._plugin import MailOutPlugin


class MailSmtpPlugin(MailOutPlugin):
    """
    Plugin to interact with a mail server over SMTP.
    """

    @classmethod
    def _matches_url_scheme(cls, scheme: str) -> bool:
        return scheme in ('smtp', 'smtps')

    @classmethod
    def default_ports(cls) -> Dict[TransportEncryption, int]:
        return {
            TransportEncryption.NONE: 25,
            TransportEncryption.SSL: 465,
            TransportEncryption.STARTTLS: 587,
        }

    @contextmanager
    def connect(self) -> Generator[SMTP, None, None]:
        smtp_args = {
            'host': self.server.server,
            'port': self.server.port,
        }

        if self.server.encryption == TransportEncryption.SSL:
            client_type = SMTP_SSL
            if self.server.certfile:
                smtp_args.update(certfile=self.server.certfile)
            if self.server.keyfile:
                smtp_args.update(keyfile=self.server.keyfile)
        else:
            client_type = SMTP

        client = client_type(**smtp_args)

        if self.server.encryption == TransportEncryption.STARTTLS:
            client.ehlo()
            client.starttls()
        else:
            client.ehlo_or_helo_if_needed()

        pwd = None
        try:
            pwd = self.account.get_password()
        except AssertionError:
            pass

        if pwd:
            client.login(self.account.username, pwd)

        yield client
        client.quit()

    def send_message(self, message: Message, **_):
        with self.connect() as client:
            errors = client.sendmail(
                message['From'], message['To'], message.as_string()
            )

        if errors:
            raise AssertionError(
                'Failed to send message: '
                + str([f'{code}: {err}' for code, err in errors.items()])
            )


# vim:sw=4:ts=4:et:
