from sqlalchemy import asc
from .libnotify.notification import Notification
from .libnotify.service import NotificationService
from .. import configuration as config
from .. import Session
from ..decorators import background
from ..models import NotificationEmailData
import logging
import os
import smtplib


ROOT_DIR = os.path.abspath(os.path.dirname(__file__))


@background
def notify():
    session = Session()
    if config.SMTP_USE_SSL:
        smtp = smtplib.SMTP_SSL(host=config.SMTP_SERVER, port=config.SMTP_PORT)
    else:
        smtp = smtplib.SMTP(host=config.SMTP_SERVER, port=config.SMTP_PORT)
    if config.SMTP_USE_TLS:
        smtp.starttls()
    try:
        smtp.login(config.SMTP_USERNAME, config.SMTP_PASSWORD)
    except smtplib.SMTPHeloError:
        logging.warning('The server didn’t reply properly to the HELO '
                        'greeting.')
        return
    except smtplib.SMTPAuthenticationError as e:
        logging.warning('The server didn’t accept the username/password '
                        'combination.')
        logging.warning(e)
        return
    except smtplib.SMTPNotSupportedError:
        logging.warning('The AUTH command is not supported by the server.')
        return
    except smtplib.SMTPException:
        logging.warning('No suitable authentication method was found.')
        return
    notification_service = NotificationService(smtp)
    # create notifications (content, recipient etc.)
    notifications = __create_mail_notifications(notification_service, session)
    # only login and send mails if there are any notifications
    if (len(notifications) > 0):
        # combine new and unsent notifications
        notifications.update(notification_service.not_sent)
        # send all notifications
        __send_mail_notifications(notifications, notification_service)
        # remove unsent notifications because they have been sent now
        # but only if mail limit has not been exceeded
        if (notification_service.mail_limit_exceeded is not True):
            notification_service.not_sent = {}
    smtp.quit()
    Session.remove()


# Email notification functions
def __create_mail_notifications(notification_service, session):
    notification_email_data = session.query(NotificationEmailData).order_by(asc(NotificationEmailData.creation_date)).all()  # noqa
    notifications = {}
    for data in notification_email_data:
        notification = Notification()
        notification.set_addresses(config.SMTP_DEFAULT_SENDER,
                                   data.job.user.email)
        subject_template = ('[nopaque] Status update for your Job/Corpora: '
                            '{title}!')
        subject_template_values_dict = {'title': data.job.title}
        url = '{}://{}/{}/{}'.format(config.PROTOCOL,
                                     config.DOMAIN,
                                     'jobs',
                                     data.job.id)
        body_template_values_dict = {'username': data.job.user.username,
                                     'id': data.job.id,
                                     'title': data.job.title,
                                     'status': data.notify_status,
                                     'time': data.creation_date,
                                     'url': url}
        txt_tmplt = os.path.join(ROOT_DIR,
                                 'libnotify/templates/notification.txt')
        html_tmplt = os.path.join(ROOT_DIR,
                                  'libnotify/templates/notification.html')
        notification.set_notification_content(subject_template,
                                              subject_template_values_dict,
                                              txt_tmplt,
                                              html_tmplt,
                                              body_template_values_dict)
        notifications[data.job.id] = notification
        # Using a dictionary for notifications avoids sending multiple mails
        # if the status of a job changes in a few seconds. The user will not
        # get swamped with mails for queued, running and complete if those
        # happen in in a few seconds. Only the last update will be sent.
        # This depends on the sleep time interval though.
        session.delete(data)
        session.commit()
    return notifications


def __send_mail_notifications(notifications, notification_service):
    for key, notification in notifications.items():
        try:
            notification_service.send(notification)
            notification_service.mail_limit_exceeded = False
        except Exception:
            # Adds notifications to unsent if mail server exceded limit for
            # consecutive mail sending
            logging.warning('limit')
            notification_service.not_sent[key] = notification
            notification_service.mail_limit_exceeded = True
            notification_service.not_sent.update(notifications)