from email.header import decode_header
from plone import api
from plone.app.textfield.value import RichTextValue
from plone.base.utils import safe_text
from plone.namedfile.file import NamedBlobFile
from plone.namedfile.file import NamedBlobImage
from ploneintranet import api as pi_api
from ploneintranet.search.interfaces import IExcludeFromSearchResults
from ploneintranet.workspace import utils
from Products.CMFCore.interfaces import IFolderish
from zope import event
from zope.component import adapter
from zope.container.contained import ObjectAddedEvent
from zope.interface import alsoProvides

import logging
import quopri
import re

log = logging.getLogger(__name__)


def get_recipients_from_msg(msg, address_type):
    """Get the addresses from message.

    address_type is one of TO, CC, BCC, ...
    """
    addresses = safe_text(msg.get(address_type, "").strip())
    if not addresses:
        return ()
    return tuple(address.strip() for address in addresses.split(","))


class BaseImporter:
    """Create objects from an email"""

    mailin_keyworkds = []

    def decode(self, text):
        """Perform some magin on text to decode it

        text should be a filename

        TODO: explain this better!
        """
        enc_re = re.compile(r"=\?([^\?]*)\?([BQ])\?(.*)\?=")
        # get rid of line breaks
        lines = text.splitlines()
        decoded = []
        for line in lines:
            match = enc_re.search(line)
            if match:
                if match.group(2) == "B":
                    line = match.group(3).decode("base64")
                elif match.group(2) == "Q":
                    line = quopri.decodestring(match.group(3))
                line = line.decode(match.group(1))
            decoded.append(line)
        return "".join(decoded)

    def create_attachment(self, mail, part):
        """Create an attachment for this mail"""
        cid = part["Content-Id"]
        file_name = part.get_filename() or cid
        content_type = part.get_content_type()
        payload = part.get_payload(decode=True)

        type_name = (
            self.registry.findTypeName(file_name, content_type, payload) or "File"
        )

        obj = api.content.create(type=type_name, title=file_name, container=mail)
        if type_name == "File":
            obj.file = NamedBlobFile(data=payload, filename=str(file_name))
        elif type_name == "Image":
            obj.image = NamedBlobImage(data=payload, filename=str(file_name))
        else:
            primary = utils.get_primary_field(obj)
            if primary:
                primary[1].set(obj, payload)
            else:
                log.warning("Discarding paylod for %s", obj.UID())

        obj.setSubject(self.mailin_keyworkds)
        alsoProvides(obj, IExcludeFromSearchResults)
        event.notify(ObjectAddedEvent(obj))
        return obj


@adapter(IFolderish)
class MailImporter(BaseImporter):
    """IMailImportAdapter which imports an email into a content object
    inside the appropriate workspace.
    """

    # NB this code is called from a helper view.
    # So you can't just pdb it, it'll swallow silently.
    mail_portal_type = "ploneintranet.workspace.mail"
    mailin_keyworkds = ["email upload"]

    def __init__(self, context):
        self.context = context
        self.registry = api.portal.get_tool("content_type_registry")

    def get_parts(self, msg):
        """Returns the parts as a dictionary"""
        parts = {"attachments": []}
        msg_parts = (part for part in msg.walk() if not part.is_multipart())
        for part in msg_parts:
            cdisposition = part.get("Content-Disposition")
            if cdisposition is None:
                ctype = part.get("Content-Type")
                if "text/html" in ctype:
                    parts["html"] = part
                else:
                    parts["text"] = part
            else:
                parts["attachments"].append(part)
        return parts

    def create_mail(self, msg):
        """Create the mail looking in to"""
        title, encoding = decode_header(msg.get("Subject"))[0]
        if encoding:
            title = safe_text(safe_text(title, encoding))
        container = self.get_or_create_import_folder()
        mail = api.content.create(
            type=self.mail_portal_type,
            title=title,
            container=container,
            # ids are generated by the context workspace, not the import folder
            # so should be safe to move within the workspace
            id=self.context.generateId("mail-"),
            safe_id=False,
        )
        mail.manage_addLocalRoles(api.user.get_current().id, ["Owner"])

        mail.mail_from = safe_text(msg.get("From", ""))
        mail.mail_to = get_recipients_from_msg(msg, "TO")
        mail.mail_cc = get_recipients_from_msg(msg, "CC")
        mail.mail_bcc = get_recipients_from_msg(msg, "BCC")
        mail.subject = self.mailin_keyworkds
        return mail

    def get_or_create_import_folder(self):
        """While this adapter is registered for IFolderish,
        this code makes most sense if self.context is a workspace.
        It'll happily create and/or return a subfolder though.
        """
        mailin_folder_id = api.portal.get_registry_record(
            "ploneintranet.workspace.mailin.folder.id", default=""
        )
        if not mailin_folder_id:
            # default to the workspace itself
            return self.context
        elif mailin_folder_id in list(self.context.keys()):
            return self.context[mailin_folder_id]
        else:
            mailin_folder_title = (
                api.portal.get_registry_record(
                    "ploneintranet.workspace.mailin.folder.title", default=""
                )
                or mailin_folder_id
            )
            return api.content.create(
                self.context, "Folder", mailin_folder_id, mailin_folder_title
            )

    def get_payload(self, part, encoding="utf8"):
        """Gets the encoded payload from part"""
        return safe_text(
            safe_text(part.get_payload(decode=True), part.get_content_charset()),
            encoding,
        )

    def set_mail_body(self, mail, parts, attachments):
        """Set's the body for this email

        For html mail it takes care of fixing eventual links to the contents,
        that for emails rea something like src="cid:content-id"

        The attachment dictionary maps the cid to the objects
        attachments = {
            None: <File at /plone/.../signature.asc>,
            '<6B1...00>': <Image at /plone/.../quaive.jpg>
        }
        """
        if "html" in parts:
            body = self.get_payload(parts["html"])
            # 'cid:6B1EAA1C-F248-48C8-BC2F-3E8F2633E02E@Speedport_W_921V_1_39_000'
            for key in attachments:
                if key:
                    body = body.replace(
                        'src="cid:%s"' % key[1:-1],
                        'src="resolveuid/%s"' % attachments[key].UID(),
                    )
        elif "text" in parts:
            body = self.get_payload(parts["text"])
        else:
            body = ""

        mail.mail_body = RichTextValue(body)

    def store_original_message(self, mail, msg):
        """Add the original message as file"""
        obj = api.content.create(
            container=mail,
            type="File",
            title="email.eml",
            file=NamedBlobFile(data=str(msg), filename="original-email.eml"),
        )
        return obj

    def create_statusupdates(self, mail):
        """Create the status updates related to the received email"""
        pi_api.microblog.statusupdate.create(
            content_context=mail, action_verb="created", tags=mail.Subject()
        )

    def add(self, msg):
        """Create an email with attachments inside this objr"""
        pi_api.events.disable_microblog()
        # Extract the various parts
        mail = self.create_mail(msg)
        parts = self.get_parts(msg)
        attachments = {
            part["Content-Id"]: self.create_attachment(mail, part)
            for part in parts["attachments"]
        }
        self.set_mail_body(mail, parts, attachments)

        self.store_original_message(mail, msg)
        self.create_statusupdates(mail)
        pi_api.events.enable_microblog()
        return mail
