#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: __init__.py 9439 2015-09-25 08:01:30Z Eric $
#
# Copyright (c) 2015 Nuwa Information Co., Ltd, All Rights Reserved.
#
# Licensed under the Proprietary License,
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at our web site.
#
# See the License for the specific language governing permissions and
# limitations under the License.
#
# $Author: Eric $ (last)
# $Date: 2015-09-25 16:01:30 +0800 (Fri, 25 Sep 2015) $
# $Revision: 9439 $
#
# Contributors:
#  Bear (original author)
#  Yachu
"""
Email backends to send mail via cloud architecture.
"""

import sys
import traceback
import logging

logger = logging.getLogger(__name__)

def attachSettings(settingsLocals, backends, preferred, config):
    """
    Attach 'mail' related settings to settings.py.

    @param settingsLocals locals() in settings.py.
    @param backends Backend instances.
    @param preferred Preferred backends.
    @param config Configuration settings.
    """
    from Iuppiter.DjangoUtil import extendInstalledApps    
    
    extendInstalledApps(settingsLocals, (
        'django_ses',
        'Iuno.cloud.mail',
    ))

    settingsLocals['CELERY_ROUTES'].update({
        "Iuno.cloud.mail.tasks.send": {
            "queue": "mail",
        },
    })

from django.core.mail.backends.base import BaseEmailBackend
from django.core.mail.backends.smtp import EmailBackend as SMTPBackend

class SendMessageException(Exception):
    def __init__(self, exception):
        self.exception = exception

    def __str__(self):
        return "Send message failed, reason: %s" % str(self.exception)

class RetryException(Exception):
    def __init__(self, exception):
        self.exception = exception

    def __str__(self):
        return "Retry failed, reason: %s" % str(self.exception)

class RetryAndSendException(Exception):
    def __init__(self, retryException, exception):
        self.exception = exception
        self.retryException = retryException

    def __str__(self):
        return 'Send failed, reason: "%s", and retry failed, reason: "%s".' % (
               str(self.exception), str(self.retryException))

class EmailBackend(BaseEmailBackend):
    """
    A Django Email backend that send mail via cloud with blocking way.
    """
    def __init__(self, host=None, port=None, username=None, password=None,
                 use_tls=None, fail_silently=False, fallback=True, **kwargs):
        super(EmailBackend, self).__init__(fail_silently=fail_silently)
        if fallback:
            self.smtpSender = SMTPBackend(host, port, username, password,
                                          use_tls, fail_silently, **kwargs)
        else:
            self.smtpSender = None

        self.kwargs = kwargs

    def send_messages(self, email_messages):
        if not email_messages:
            return

        ex = None
        numSent = 0
        try:
            # We must reset connection to None to make it be able to pickle.
            for message in email_messages:
                message.connection = None

            from Iuno.cloud.mail import tasks
            sendResult = tasks.send.delay(email_messages,
                fail_silently=False, **self.kwargs).get()

            if isinstance(sendResult, tuple):
                numSent, sesExtra = sendResult
                # Append SES extra messages to email_messages.
                if sesExtra:
                    for extra in sesExtra:
                        messageId = extra['message_id']
                        requestId = extra['request_id']
                        message.extra_headers['status'] = 200
                        message.extra_headers['message_id'] = messageId
                        message.extra_headers['request_id'] = requestId
            else:
                numSent = sendResult

        except Exception, ex:
            logger.debug('send task failed: %s:%s' % (type(ex), str(ex)))
            logger.debug(''.join(traceback.format_exception(*sys.exc_info())))
            
            # Two possibilities, one is SES fail, one is celery fail.
            if not self.smtpSender and not self.fail_silently:
                # No fallback, and not fail silently.
                raise SendMessageException(ex)

            # TODO: if celery fail, we might be able to try to send by SES
            #       directly by calling tasks.send.

        if self.smtpSender:
            retrys = []
            if numSent != len(email_messages):
                for message in email_messages:
                    # Not sent.
                    if message.extra_headers.get('status', None) != 200:
                        retrys.append(message)

            # Finally try native SMTP sender.
            if retrys:
                # if fail_silently is False, we have passed to smptSender,
                # so it will raise.
                try:
                    fallbackNumSent = self.smtpSender.send_messages(retrys)
                    numSent += fallbackNumSent
                    logger.info('fallback to SMTP backend: %d sent' % 
                                fallbackNumSent)
                except Exception as retryEx:
                    if not self.fail_silently and ex:
                        raise RetryAndSendException(retryEx, ex)
                    else:
                        raise RetryException(retryEx)

        return numSent

class AsyncEmailBackend(BaseEmailBackend):
    """
    A Django Email backend that send mail via cloud with non-blocking way.

    Please note we always don't know the mail sent or not, it only promise
    to let you know how many mails are sent to cloud not recipients.
    """
    def __init__(self, fail_silently=False, **kwargs):
        super(AsyncEmailBackend, self).__init__(fail_silently=fail_silently)
        self.kwargs = kwargs

    def send_messages(self, email_messages):
        if not email_messages:
            return

        try:
            # We must reset connection to None to make it be able to pickle.
            for message in email_messages:
                message.connection = None

            from Iuno.cloud.mail import tasks
            tasks.send.delay(email_messages, fail_silently=False,
                             **self.kwargs)
        except Exception, ex:
            logger.debug('send task failed: %s:%s' % (type(ex), str(ex)))
            logger.debug(''.join(traceback.format_exception(*sys.exc_info())))
            
            # Celery fail.
            if not self.fail_silently:
                raise

        return len(email_messages)
