#!/usr/bin/python3

# Copyright (C) 2019-2020
#
# * Felix Delattre <felix@delattre.de>
# * W. Martin Borgert <debacle@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import re
import imaplib
import smtplib
import asyncio
import quopri
from asgiref.sync import sync_to_async
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr


class GatewayEmail(object):

    PHONE_RE = re.compile(r"^\+?[0-9]+$")

    def __init__(self, config, loop, logger):
        self.config = config
        self.logger = logger
        self.loop = loop
        self.gateway = None

    def setup(self, gateway):
        self.gateway = gateway
        self.loop.create_task(self._prepare())

    async def _prepare(self):
        while True:
            number, message = await self.message()
            if number is not None:
                self.logger.log.debug("Email passed on to SMS")
                await self.gateway.dispatch(number, message)
            await asyncio.sleep(60)

    @sync_to_async
    def message(self):

        self.logger.log.debug("Email check messages")
        number = None
        message = None

        # Open connection to IMAP server
        imap = imaplib.IMAP4_SSL(self.config.email_imap_host, self.config.email_imap_port)

        # Login and get list of unread emails
        try:
            imap.login(self.config.email_username, self.config.email_password)
            imap.select("INBOX")
            status, response = imap.uid("search", None, "UNSEEN")
            unread_messages = response[0].split()
        except imaplib.IMAP4.error as e:
            self.logger.log.debug("Error: Failed to check emails: %s", e)

        # Fetch email contents
        if unread_messages:
            _, response = imap.uid(
                "fetch",
                unread_messages[0],
                "(BODY[1] BODY[HEADER.FIELDS (SUBJECT)] BODY[HEADER.FIELDS (FROM)])",
            )
            number, message, sender = self._format_message(
                response[1][1], response[0][1], response[2][1]
            )
            self.logger.log.debug("Email received")

            if sender not in self.config.sender_emails:
                warning = "Email address is not allowed to send SMS"
                self.logger.log.debug("%s: %s", warning, sender)
                self._send_message(self.config.email_username, sender, number, warning)
                number = None

            elif not self.PHONE_RE.match(number):
                warning = "Email contains invalid phone number"
                self.logger.log.debug("%s: %s", warning, number)
                self._send_message(self.config.email_username, sender, number, warning)
                number = None

            elif len(message) > 160:  # SMS cannot exceed 160 characters
                number = None
                warning = "Email message is too long for a SMS"
                self.logger.log.debug(warning)
                self._send_message(self.config.email_username, sender, number, warning)
                number = None

        # Close imap server session
        imap.logout()

        return number, message

    @sync_to_async
    def dispatch(self, number, smsc, message, timestamp):

        self.logger.log.debug("Email prepared")

        for recipient_email in self.config.recipient_emails:
            self._send_message(self.config.email_username, recipient_email, number, message)
            self.logger.log.debug("Email sent to: %s", recipient_email)

    def _send_message(self, sender, recipient, subject, message):

        # Prepare email
        msg = MIMEMultipart()
        msg["From"] = sender
        msg["To"] = recipient
        msg["Subject"] = subject
        msg.attach(MIMEText(message, "plain", "utf-8"))

        # Send email
        server = smtplib.SMTP_SSL(self.config.email_smtp_host, self.config.email_smtp_port)
        server.login(self.config.email_username, self.config.email_password)
        server.sendmail(self.config.email_username, recipient, msg.as_string())

        # Close smtp server
        server.quit()

    def _format_message(self, number, message, sender):

        try:
            number = next(
                iter(re.findall(r"[\+\(]?[1-9][0-9 .\-\(\)]{8,}[0-9]", number.decode()))
            )
        except Exception:
            number = "Re: " + number.decode()
        message = quopri.decodestring(message).decode()
        address = parseaddr(sender.decode())[1]

        return number, message, address
