from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from io import StringIO as CSVIO
from plone import api
from plone.memoize.view import memoize
from ploneintranet import api as pi_api
from ploneintranet.core import _
from ploneintranet.layout.interfaces import IDiazoDashboardTemplate
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from z3c.relationfield.relation import RelationValue
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
from zope.intid.interfaces import IIntIds
from zope.lifecycleevent import ObjectModifiedEvent

import csv
import logging
import re

logger = logging.getLogger(__name__)


def get_invitees(context):
    """invitees is stored as a comma separated string of user ids
    or emails addresses.

    This function transforms them into users
    """
    invitees = context.invitees
    if not invitees or not isinstance(invitees, str):
        return []
    invitees = invitees.split(",")
    principalids = set()
    # BBB: we may want to think to a better regexp to extract principalid
    # when the invitee looks like "John Doe <john.doe@example.net>"
    pattern = re.compile(r"(.*)<(.*)>")
    for invitee in invitees:
        match = pattern.match(invitee)
        if match:
            principalids.add(match.groups()[-1])
        else:
            principalids.add(invitee)

    return pi_api.userprofile.get_users_from_userids_and_groupids(principalids)


def get_invitee_details(context, skip_no_emails=True):
    """invitees is stored as a comma separated string of user ids
    or emails addresses.

    :rtype list of dicts: [{
        'id': 'user_id', 'name' 'user_title', 'email': 'user_email'}]
    """
    details = []
    invitees = get_invitees(context)
    for invitee in invitees:
        email = getattr(invitee, "email", "")
        if not email and skip_no_emails:
            continue
        userid = invitee.getId()
        name = invitee.fullname or userid
        details.append({"id": invitee, "name": name, "email": email, "uid": userid})
    return details


class EmailInvitees(BrowserView):
    """Email the invitees a message with a link to the event"""

    message_html = ViewPageTemplateFile("templates/email_invitees_html.pt")

    def get_message_html(self):
        """Get and compile an email message in html format"""
        return self.message_html()

    @memoize
    def get_user(self):
        """Get the currently authenticated userprofile"""
        return pi_api.userprofile.get_current()

    @memoize
    def get_sender(self):
        """Return the email sender"""
        user = self.get_user()
        if user:
            name = user.fullname
            address = user.email
            return f"{name} <{address}>"

    @memoize
    def get_sender_fullname(self):
        """Return the current user fullname"""
        user = self.get_user()
        if user:
            return user.fullname

    def get_subject(self):
        """Return the email subject based on this event title and dates"""
        return "{}: {}".format(
            self.context.title, self.context.start.strftime("%Y/%m/%d %H:%M")
        )

    def iter_messages(self):
        """Return the payload to deliver a s mime/multipart message"""
        invitees = get_invitee_details(self.context)

        for invitee in invitees:
            # FIXME this is used in the mail template
            self.invitee_name = invitee["name"]
            recipient = "{} <{}>".format(self.invitee_name, invitee["email"])
            msg = MIMEMultipart("alternative")
            # To and Subject should not use Header here, because it is already used by
            # MailHost and will throw an error
            msg["To"] = recipient
            msg["Subject"] = self.get_subject()

            reply_to = self.get_sender()
            if reply_to:
                msg["Reply-To"] = Header(reply_to, "utf-8")
            html = self.get_message_html()
            part = MIMEText(html, "html")
            part.set_charset("utf-8")
            msg.attach(part)
            yield msg

    @memoize
    def send(self):
        """Send the messages and return the errors"""
        errors = 0
        for msg in self.iter_messages():
            try:
                api.portal.send_email(
                    recipient=msg["To"],
                    subject=msg["Subject"],
                    body=msg,
                    immediate=True,
                )
            except Exception as error:
                logger.error(f"MailHost error: {error}")
                api.portal.show_message(_("Error sending email"), self.request, "error")
                errors += 1
        return errors

    def __call__(self):
        """Send email and report on success of failures"""
        errors = self.send()
        if errors == 0:
            api.portal.show_message(
                _("Emails sent successfully"), self.request, "success"
            )
        else:
            api.portal.show_message(
                "%d errors, see logs for more details" % errors, self.request, "error"
            )
        # self.request.response.redirect(self.context.absolute_url())
        msg = ViewPageTemplateFile("templates/globalstatusmessage.pt")
        return msg(self)


@implementer(IDiazoDashboardTemplate)
class PromoView(BrowserView):
    """A view to promote an event"""

    _attending_options_mapping = {
        "option-1": {
            "good_list": "participants_yes",
            "bad_lists": ["participants_maybe", "participants_no"],
        },
        "option-2": {
            "good_list": "participants_maybe",
            "bad_lists": ["participants_yes", "participants_no"],
        },
        "option-3": {
            "good_list": "participants_no",
            "bad_lists": ["participants_yes", "participants_maybe"],
        },
    }

    @property
    @memoize
    def user(self):
        return pi_api.userprofile.get_current()

    @property
    @memoize
    def user_intid(self):
        user = self.user
        if not user:
            return

        intids = getUtility(IIntIds)
        return intids.getId(user)

    @property
    @memoize
    def attending_value(self):
        attending = self.request.get("attending")
        if attending:
            return attending
        user_intid = self.user_intid
        if not user_intid:
            return

        mapping = self._attending_options_mapping
        for option in mapping:
            relations = getattr(self.context, mapping[option]["good_list"])
            for relation in relations:
                if user_intid == relation.to_id:
                    return option

    def maybe_attending(self):
        """Check if the user has submitted a preference about attending this event"""
        attending = self.request.get("attending")
        if attending not in self._attending_options_mapping:
            return

        user_intid = self.user_intid
        if not user_intid:
            return

        mapping = self._attending_options_mapping[attending]
        good_list = mapping["good_list"]
        bad_lists = mapping["bad_lists"]

        relation_list = getattr(self.context, good_list)
        good_list_intids = [relation.to_id for relation in relation_list]

        modified = False
        if user_intid not in good_list_intids:
            value = RelationValue(user_intid)
            modified = True
            if not good_list_intids:
                setattr(self.context, good_list, [value])
            else:
                relation_list.append(value)

        for bad_list in bad_lists:
            relation_list = getattr(self.context, bad_list)
            bad_list_intids = [relation.to_id for relation in relation_list]
            if user_intid in bad_list_intids:
                modified = True
                relation_list.pop(bad_list_intids.index(user_intid))
        if modified:
            notify(ObjectModifiedEvent(self.context))

    def attached_documents_grouped(self):
        grouping = {"image": [], "video": [], "other": []}

        for item in self.context.relatedItems:
            obj = item.to_object
            key = (obj.content_type() or "").partition("/")[0]
            if key not in grouping:
                key = "other"
            grouping[key].append(obj)
        return grouping

    def is_same_date(self):
        end = self.context.end
        if not end:
            return False
        return self.context.start.date() == end.date()

    def __call__(self):
        self.maybe_attending()
        return super().__call__()


class PromoRSVPCSV(BrowserView):
    """Download respondents to the @@promo_voting_bar in CSV format."""

    def __call__(self):
        intids = getUtility(IIntIds)
        csvstream = CSVIO()
        writer = csv.writer(csvstream, delimiter=",", quoting=csv.QUOTE_ALL)
        writer.writerow(["username", "email", "fullname", "yes", "maybe", "no"])
        for user_relation in self.context.participants_yes:
            user = intids.getObject(user_relation.to_id)
            writer.writerow([user.username, user.email, user.fullname, 1, 0, 0])
        for user_relation in self.context.participants_maybe:
            user = intids.getObject(user_relation.to_id)
            writer.writerow([user.username, user.email, user.fullname, 0, 1, 0])
        for user_relation in self.context.participants_no:
            user = intids.getObject(user_relation.to_id)
            writer.writerow([user.username, user.email, user.fullname, 0, 0, 1])
        self.request.response.setHeader("Content-type", "text/csv")
        self.request.response.setHeader(
            "Content-disposition", f"inline; filename={self.context.id}.csv"
        )
        return csvstream.getvalue()
