# NateMan – Nachschreibtermin-Manager
# emails.py
# Copyright © 2020  Niklas Elsbrock
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""
Enthält Funktionen für den E-Mail-Versand.
"""

import smtplib
from datetime import datetime
from email.mime.text import MIMEText
from typing import Optional, Union

from flask import current_app, render_template
from sqlalchemy.sql.elements import and_

from .config_manager import config
from .models import Klausur, Lehrer


def _smtp_create() -> Union[smtplib.SMTP, smtplib.SMTP_SSL]:
    """Erstellt die der Konfiguration entsprechende SMTP-Instanz und verbindet zum SMTP-Server."""
    logger = current_app.logger
    logger.debug("Stellt Verbindung zum SMTP-Server her...")

    smtp_constr = smtplib.SMTP_SSL if config["mail"]["use-ssl"] else smtplib.SMTP
    smtp = smtp_constr(config["mail"]["smtp"]["hostname"], config["mail"]["smtp"]["hostport"])

    smtp.login(config["mail"]["smtp"]["user"], config["mail"]["smtp"]["password"])

    logger.debug("Erfolgreich mit SMTP-Server verbunden.")

    return smtp


def _send_mail(name: str, address: str, subject: str, content: str, content_type="html",
               content_charset="utf-8", smtp: Optional[smtplib.SMTP] = None) -> None:
    """
    Sendet eine E-Mail mit den in der NateMan-Konfiguration angegebenen SMTP-Daten.

    :param name: Name des Empfängers
    :param address: Adresse des Empfängers
    :param subject: Betreff der E-Mail
    :param content: Inhalt der E-Mail
    :param content_type: Inhaltstyp der E-Mail
    :param content_charset: Zeichensatz des E-Mail-Inhalts
    :param smtp: SMTP-Verbindung. Falls keine Verbindung angegeben wird, wird eine erstellt
    (und nach dem E-Mail-Versand geschlossen).
    """
    logger = current_app.logger
    sender_address = config["mail"]["sender-address"]

    msg = MIMEText(content, content_type, content_charset)
    msg["From"] = f"NateMan <{sender_address}>"
    msg["To"] = f"{name} <{address}>"
    msg["Subject"] = subject

    smtp_param = smtp is not None
    if not smtp_param:
        smtp = _smtp_create()

    logger.debug(f"E-Mail wird an {address} versandt...")
    try:
        smtp.sendmail(sender_address, address, msg.as_string())
    except smtplib.SMTPRecipientsRefused as exc:
        if not smtp_param:
            smtp.close()
        logger.debug(f"E-Mail an {address} konnte nicht versandt werden "
                     f"(wahrscheinlich ungültige Adresse).", exc_info=exc)
        raise
    else:
        logger.debug(f"E-Mail an {address} erfolgreich versandt.")

    if not smtp_param:
        smtp.close()


def send_confirmation_link_mail(name: str, address: str, token: str) -> None:
    """Sendet die Bestätigungsemail nach dem Festlegen der E-Mail-Adresse."""
    content = render_template("email/confirmation.html.j2", token=token)
    _send_mail(name, address, "NateMan: Bestätigung der E-Mail-Adresse", content)


def send_password_reset_mail(name: str, address: str, token: str) -> None:
    """Sendet die Passwortzurücksetzungsemail."""
    content = render_template("email/password-reset.html.j2", token=token)
    _send_mail(name, address, "NateMan: Passwortzurücksetzung", content)


def send_reminder_mails() -> int:
    """
    Sendet Erinnerungsemails an alle Lehrer, die unbearbeitete vergangene Klausuren haben.

    :return: Anzahl der E-Mails, die nicht versandt werden konnten
    """
    logger = current_app.logger
    logger.info("Startet den Versand von Erinnerungsemails...")

    # SMTP-Connect
    smtp = _smtp_create()

    fail_count = 0
    for lehrer in Lehrer.query.all():
        not_edited_list = Klausur.query \
            .filter(and_(Klausur.date <= datetime.now().date(), Klausur.lehrer == lehrer, ~Klausur.edited)) \
            .order_by(Klausur.date.desc()).all()

        if len(not_edited_list) == 0:
            fail_count += 1
            continue

        if lehrer.email is None:
            fail_count += 1
            logger.info(f"Erinnerungsemail an {lehrer} konnte nicht versandt werden, da für diesen Lehrer keine "
                        f"E-Mail-Adresse eingetragen ist.")
            continue

        if not lehrer.is_confirmed:
            fail_count += 1
            logger.info(f"Erinnerungsemail an {lehrer} konnte nicht versandt werden, da die E-Mail-Adresse dieses "
                        f"Lehrers ({lehrer.email}) nicht bestätigt ist.")
            continue

        content = render_template("email/reminder.html.j2", not_edited_list=not_edited_list)
        try:
            _send_mail(lehrer.kuerzel, lehrer.email, "NateMan: zur Erinnerung", content, smtp=smtp)
        except smtplib.SMTPRecipientsRefused as exc:
            logger.info(f"Erinnerungsemail an {lehrer} mit der Adresse {lehrer.email} konnte nicht versandt werden "
                        f"(wahrscheinlich ungültige Adresse).", exc_info=exc)
        else:
            logger.info(f"Erinnerungsemail an {lehrer} erfolgreich versandt.")

    smtp.close()

    if fail_count == 0:
        logger.info("Versand von Erinnerungsemails abgeschlossen.")
    else:
        logger.info(f"Versand von Erinnerungsemails abgeschlossen. "
                    f"{fail_count} E-Mail(s) konnten nicht versandt werden.")

    return fail_count
