__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
import time
import re
import json
import inspect
from html2text import HTML2Text
from django.core.mail import EmailMultiAlternatives
from django.contrib.auth import get_user_model
from django.conf import settings
from django.template import loader
from scipost.models import Contributor
from common.utils import get_current_domain
[docs]class MailUtilsMixin:
    """
    This mixin takes care of inserting the default data into the Utils or Form.
    DEPRECATED
    """
    instance = None
    mail_data = {}
    mail_template = ""
    html_message = ""
    message = ""
    original_recipient = ""
    mail_sent = False
    delayed_processing = False
    def __init__(self, *args, **kwargs):
        """Init an instance for a specific mail_code.
        Arguments:
        -- mail_code (str)
        -- subject (str)
        -- to (str): Email address or relation on the `instance`. Separated by comma.
        -- bcc_to (str, optional): Email address or relation on the `instance`. Separated by comma.
        -- instance: Instance of central object in email.
        -- from (str, optional): Plain email address.
        -- from_name (str, optional): Display name for from address.
        """
        self.pre_validation(*args, **kwargs)
        super().__init__(*args)
[docs]    def pre_validation(self, *args, **kwargs):
        """Validate the incoming data to initiate a specific mail."""
        self.mail_code = kwargs.pop("mail_code")
        self.instance = kwargs.pop("instance", None)
        kwargs["object"] = self.instance  # Similar template nomenclature as Django.
        self.mail_data = {
            "subject": kwargs.pop("subject", ""),
            "to_address": kwargs.pop("to", ""),
            "bcc_to": kwargs.pop("bcc", ""),
            "from_address_name": kwargs.pop("from_name", "SciPost"),
            "from_address": kwargs.pop(
                "from", "no-reply@%s" % get_current_domain()
            ),
        }
        # Gather meta data
        json_location = "%s/templates/email/%s.json" % (
            settings.BASE_DIR,
            self.mail_code,
        )
        try:
            self.mail_data.update(json.loads(open(json_location).read()))
        except OSError:
            if not self.mail_data["subject"]:
                raise NotImplementedError(
                    (
                        "You did not create a valid .html and .json file "
                        "for mail_code: %s" % self.mail_code
                    )
                )
        # Save central object/instance if not already
        self.instance = self.get_object(**kwargs)
        # Digest the templates
        if not self.delayed_processing:
            mail_template = loader.get_template("email/%s.html" % self.mail_code)
            if self.instance and self.mail_data.get("context_object"):
                kwargs[self.mail_data["context_object"]] = self.instance
            self.mail_template = mail_template.render(kwargs)  # Damn slow.
        # Gather Recipients data
        try:
            self.original_recipient = self._validate_single_entry(
                self.mail_data.get("to_address")
            )[0]
        except IndexError:
            self.original_recipient = ""
        self.subject = self.mail_data["subject"] 
[docs]    def get_object(self, **kwargs):
        if self.instance:
            return self.instance
        if self.mail_data.get("context_object"):
            return kwargs.get(self.mail_data["context_object"], None) 
    def _validate_single_entry(self, entry):
        """
        entry -- raw email string or path or properties leading to email mail field
        Returns a list of email addresses found.
        """
        if entry and self.instance:
            if re.match("[^@]+@[^@]+\.[^@]+", entry):
                # Email string
                return [entry]
            else:
                mail_to = self.instance
                for attr in entry.split("."):
                    try:
                        mail_to = getattr(mail_to, attr)
                        if inspect.ismethod(mail_to):
                            mail_to = mail_to()
                    except AttributeError:
                        # Invalid property/mail
                        return []
                if not isinstance(mail_to, list):
                    return [mail_to]
                else:
                    return mail_to
        elif re.match("[^@]+@[^@]+\.[^@]+", entry):
            return [entry]
        else:
            return []
[docs]    def validate_bcc_list(self):
        """
        bcc_to in the .json file may contain multiple raw email addreses or property paths to
        an email field. The different entries need to be comma separated.
        """
        # Get recipients list. Try to send through BCC to prevent privacy issues!
        self.bcc_list = []
        if self.mail_data.get("bcc_to"):
            for bcc_entry in self.mail_data["bcc_to"].split(","):
                self.bcc_list += self._validate_single_entry(bcc_entry) 
[docs]    def validate_recipients(self):
        # Check the send list
        if isinstance(self.original_recipient, list):
            recipients = self.original_recipient
        elif not isinstance(self.original_recipient, str):
            try:
                recipients = list(self.original_recipient)
            except TypeError:
                recipients = [self.original_recipient]
        else:
            recipients = [self.original_recipient]
        recipients = list(recipients)
        # Check if email needs to be taken from an instance
        _recipients = []
        for recipient in recipients:
            if isinstance(recipient, Contributor):
                _recipients.append(recipient.user.email)
            elif isinstance(recipient, get_user_model()):
                _recipients.append(recipient.email)
            elif isinstance(recipient, str):
                _recipients.append(recipient)
        self.recipients = _recipients 
[docs]    def validate_message(self):
        if not self.html_message:
            self.html_message = self.mail_template
        handler = HTML2Text()
        self.message = handler.handle(self.html_message) 
[docs]    def validate(self):
        """Execute different validation methods.
        Only to be used when the default data is used, eg. not in the EmailTemplateForm.
        """
        self.validate_message()
        self.validate_bcc_list()
        self.validate_recipients()
        self.save_mail_data() 
[docs]    def save_mail_data(self):
        """Save mail validated mail data; update default values of mail data."""
        self.mail_data.update(
            {
                "subject": self.subject,
                "message": self.message,
                "html_message": self.html_message,
                "recipients": self.recipients,
                "bcc_list": self.bcc_list,
            }
        ) 
[docs]    def set_alternative_sender(self, from_name, from_address):
        """TODO: REMOVE; DEPRECATED
        Set an alternative from address/name from the default values received from the json
        config file. The arguments only take raw string data, no methods/properties!
        """
        self.mail_data["from_address_name"] = from_name
        self.mail_data["from_address"] = from_address 
[docs]    def send(self):
        """Send the mail assuming `mail_data` is validated and complete."""
        if self.mail_sent:
            # Prevent double sending when using a Django form.
            return
        email = EmailMultiAlternatives(
            self.mail_data["subject"],
            self.mail_data["message"],
            "%s <%s>"
            % (self.mail_data["from_address_name"], self.mail_data["from_address"]),
            self.mail_data["recipients"],
            bcc=self.mail_data["bcc_list"],
            reply_to=[self.mail_data["from_address"]],
            headers={
                "delayed_processing": self.delayed_processing,
                "content_object": self.get_object(),
                "mail_code": self.mail_code,
            },
        )
        # Send html version if available
        if "html_message" in self.mail_data:
            email.attach_alternative(self.mail_data["html_message"], "text/html")
        email.send(fail_silently=False)
        self.mail_sent = True
        if self.instance and hasattr(self.instance, "mail_sent"):
            self.instance.mail_sent()