# -*- coding: utf-8 -*-
#
# django-codenerix-email
#
# Codenerix GNU
#
# Project URL : http://www.codenerix.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import re
import ssl
import smtplib
from uuid import uuid4

from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.db import models
from django.template import Context, Template
from django.core.exceptions import ValidationError
from django.conf import settings

from codenerix.models import CodenerixModel  # type: ignore
from codenerix_lib.debugger import Debugger
from codenerix.lib.genmail import (  # type: ignore # noqa: N817
    EmailMessage as EM,
    get_connection,
)
from codenerix.fields import WysiwygAngularField  # type: ignore


class EmailMessage(CodenerixModel, Debugger):
    uuid = models.UUIDField(
        _("UUID"),
        unique=True,
        default=uuid4,
        editable=False,
        null=False,
        blank=False,
    )
    efrom = models.EmailField(_("From"), blank=False, null=False)
    eto = models.EmailField(_("To"), blank=False, null=False)
    subject = models.CharField(
        _("Subject"), max_length=256, blank=False, null=False
    )
    body = models.TextField(_("Body"), blank=False, null=False)
    priority = models.PositiveIntegerField(
        _("Priority"), blank=False, null=False, default=5
    )
    sending = models.BooleanField(
        _("Sending"), blank=False, null=False, default=False
    )
    sent = models.BooleanField(
        _("Sent"), blank=False, null=False, default=False
    )
    error = models.BooleanField(
        _("Error"), blank=False, null=False, default=False
    )
    retries = models.PositiveIntegerField(
        _("Retries"), blank=False, null=False, default=0
    )
    next_retry = models.DateTimeField(_("Next retry"), auto_now_add=True)
    log = models.TextField(_("Log"), blank=True, null=True)
    opened = models.DateTimeField(
        _("Opened"), null=True, blank=True, default=None
    )

    def __fields__(self, info):
        fields = []
        fields.append(("sending", None, 100))
        fields.append(("error", None, 100))
        fields.append(("sent", _("Send"), 100))
        fields.append(("priority", _("Priority"), 100))
        fields.append(("uuid", _("UUID"), 100))
        fields.append(("created", _("Created"), 100))
        fields.append(("opened", _("Opened"), 100))
        fields.append(("efrom", _("From"), 100))
        fields.append(("eto", _("To"), 100))
        fields.append(("subject", _("Subject"), 100))
        fields.append(("retries", _("Retries"), 100))
        fields.append(("next_retry", _("Next retry"), 100))
        fields.append(("pk", _("ID"), 100))
        return fields

    def __unicode__(self):
        return "{} ({})".format(self.eto, self.pk)

    def set_opened(self):
        if not self.opened:
            self.opened = timezone.now()
            self.save()

    def connect(self, legacy=False):
        """
        This class will return a connection instance, you can disconnect it with connection.close()
        """

        if not legacy:
            host = settings.CLIENT_EMAIL_HOST
            port = settings.CLIENT_EMAIL_PORT
            username = settings.CLIENT_EMAIL_USERNAME
            password = settings.CLIENT_EMAIL_PASSWORD
            use_tls = settings.CLIENT_EMAIL_USE_TLS
        else:
            host = settings.EMAIL_HOST
            port = settings.EMAIL_PORT
            username = settings.EMAIL_USERNAME
            password = settings.EMAIL_PASSWORD
            use_tls = settings.EMAIL_USE_TLS

        # Remember last connection data
        self.__connect_info = {"host": host, "port": port, "use_tls": use_tls}
        # Get connection
        return get_connection(
            host=host,
            port=port,
            username=username,
            password=password,
            use_tls=use_tls,
        )

    def send(
        self,
        connection=None,
        legacy=False,
        silent=True,
        debug=False,
        content_subtype="plain",
    ):

        # Autoconfigure Debugger
        if debug:
            self.set_name("EmailMessage")
            self.set_debug()

        # Get connection if not connected yet
        if connection is None:
            # Connect
            if not silent or debug:
                self.warning("Not connected, connecting...")
            connection = self.connect(legacy)

        if self.eto:
            if debug:
                self.set_name("EmailMessage->{}".format(self.eto))

            # Manually open the connection
            try:
                connection.open()
            except (
                smtplib.SMTPAuthenticationError,
                OSError,
                TimeoutError,
            ) as e:
                connection = None
                if self.log is None:
                    self.log = ""
                exceptiontxt = str(type(e)).split(".")[-1].split("'")[0]
                ci = getattr(self, "__connect_info", {})
                error = "{}: {} [HOST={}:{} TLS={}]\n".format(
                    exceptiontxt,
                    e,
                    ci.get("host", "-"),
                    ci.get("port", "-"),
                    ci.get("use_tls", "-"),
                )
                self.warning(error)
                self.log += error
                # We will not retry anymore
                self.sending = False
                # We make lower this email's priority
                self.priority += 1
                # Set new retry
                self.retries += 1
                self.next_retry = timezone.now() + timezone.timedelta(
                    seconds=getattr(
                        settings, "CLIENT_EMAIL_RETRIES_WAIT", 5400
                    )
                )  # retry every 1.5h
                if self.retries >= getattr(
                    settings, "CLIENT_EMAIL_RETRIES", 10
                ):  # 10 retries * 1.5h = 15h
                    self.error = True
                # Save all
                self.save()
                if not silent:
                    raise

            if connection:
                email = EM(
                    self.subject,
                    self.body,
                    self.efrom,
                    [self.eto],
                    connection=connection,
                )
                email.content_subtype = content_subtype
                for at in self.attachments.all():
                    with open(at.path) as f:
                        email.attach(at.filename, f.read(), at.mime)

                # send list emails
                retries = 1
                while retries + 1:
                    try:
                        if connection.send_messages([email]):
                            # We are done
                            self.sent = True
                            self.sending = False
                            break
                    except ssl.SSLError as e:
                        error = "SSLError: {}\n".format(e)
                        self.warning(error)
                        self.log += error
                    except smtplib.SMTPServerDisconnected as e:
                        error = "SMTPServerDisconnected: {}\n".format(e)
                        self.warning(error)
                        self.log += error
                    except smtplib.SMTPException as e:
                        error = "SMTPException: {}\n".format(e)
                        self.warning(error)
                        self.log += error
                    finally:
                        # One chance less
                        retries -= 1
                        # Check if this is the last try
                        if not retries:
                            # We will not retry anymore
                            self.sending = False
                            # We make lower this email's priority
                            self.priority += 1
                            # Set new retry
                            self.retries += 1
                            self.next_retry = (
                                timezone.now()
                                + timezone.timedelta(
                                    seconds=getattr(
                                        settings,
                                        "CLIENT_EMAIL_RETRIES_WAIT",
                                        5400,
                                    )
                                )
                            )  # retry every 1.5h
                            if self.retries >= getattr(
                                settings, "CLIENT_EMAIL_RETRIES", 10
                            ):  # 10 retries * 1.5h = 15h
                                self.error = True
                        # Save the email
                        self.save()
                        # Disconnect
                        connection.close()
                        # Connect
                        connection = self.connect(legacy)


class EmailAttachment(CodenerixModel):
    email = models.ForeignKey(
        EmailMessage,
        on_delete=models.CASCADE,
        blank=False,
        null=False,
        related_name="attachments",
    )
    filename = models.CharField(
        _("Filename"), max_length=256, blank=False, null=False
    )
    mime = models.CharField(
        _("Mimetype"), max_length=256, blank=False, null=False
    )
    path = models.FileField(_("Path"), blank=False, null=False)

    def __fields__(self, info):
        fields = []
        fields.append(("email", _("Email"), 100))
        fields.append(("filename", _("Filename"), 100))
        fields.append(("mime", _("Mimetype"), 100))
        fields.append(("path", _("Path"), 100))
        return fields


class EmailTemplate(CodenerixModel):
    cid = models.CharField(
        _("CID"), unique=True, max_length=30, blank=False, null=False
    )
    efrom = models.TextField(_("From"), blank=True, null=False)

    def __fields__(self, info):
        fields = []
        fields.append(("pk", _("PK"), 100))
        fields.append(("cid", _("CID"), 100))
        fields.append(("efrom", _("From"), 100))
        return fields

    def __str__(self):
        return "{}:{}".format(self.cid, self.pk)

    def __unicode__(self):
        return self.__str__()

    @staticmethod
    def get(cid=None, context={}, pk=None, lang=None):
        """
        Usages:
            EmailTemplate.get('PACO', ctx) => EmailMessage(): le falta el eto
            > cid: PACO
            EmailTemplate.get(pk='PACO', context=ctx) => EmailMessage(): le falta el eto
            > pk: PACO    (we don't have CID)
        """
        if cid:
            template = EmailTemplate.objects.filter(cid=cid).first()
        else:
            template = EmailTemplate.objects.filter(pk=pk).first()

        if template:
            return template.get_email(context, lang)
        else:
            return None

    def get_email(self, context, lang=None):
        if lang is None:
            lang = settings.LANGUAGES_DATABASES[0].lower()

        e = EmailMessage()
        context["CDNX_EMAIL_emsg_uuid"] = e.uuid
        e.subject = Template(getattr(self, lang).subject).render(
            Context(context)
        )
        e.body = Template(getattr(self, lang).body).render(Context(context))
        e.efrom = Template(self.efrom).render(Context(context))
        return e

    def clean(self):
        if self.cid:
            self.cid = self.cid.upper()
            if len(re.findall(r"[A-Za-z0-9]+", self.cid)) != 1:
                raise ValidationError(
                    _(
                        "CID can contains only number and letters with no spaces"
                    )
                )


class GenText(CodenerixModel):  # META: Abstract class
    class Meta(CodenerixModel.Meta):
        abstract = True

    subject = models.TextField(_("Subject"), blank=True, null=False)
    body = WysiwygAngularField(_("Body"), blank=True, null=False)

    def __fields__(self, info):
        fields = []
        fields.append(("subject", _("Subject"), 100))
        fields.append(("body", _("Body"), 100))
        return fields

    def __unicode__(self):
        return "{}".format(self.subject)

    def __str__(self):
        return self.__unicode__()


MODELS = [
    ("email_template", "EmailTemplate"),
]

for info in MODELS:
    field = info[0]
    model = info[1]
    for lang_code in settings.LANGUAGES_DATABASES:
        query = "class {}Text{}(GenText):\n".format(model, lang_code)
        query += "  {} = models.OneToOneField({}, on_delete=models.CASCADE, blank=False, null=False, related_name='{}')\n".format(
            field, model, lang_code.lower()
        )
        exec(query)
