import logging

from django.conf import settings
from django.utils.importlib import import_module
from django.template.loader import render_to_string, TemplateDoesNotExist
from django.core.mail import EmailMultiAlternatives
import django_journal

import models

logger = logging.getLogger(__name__)

class BaseNotifier(object):
    def generate_part(self, template_path, notification):
        template_path = template_path.format(kind=notification.kind)
        try:
            ctx = {
                'notification': notification,
                'settings': settings,
            }
            if isinstance(notification.ctx, dict):
                ctx.update(notification.ctx)
            return render_to_string(template_path, ctx)
        except TemplateDoesNotExist:
            return None

    def finish(self):
        pass

class MailNotifier(BaseNotifier):
    subject_template = 'docbow/email-notification_{kind}_subject.txt'
    body_template = 'docbow/email-notification_{kind}_body.txt'
    html_body_template = 'docbow/email-notification_{kind}_body.html'

    def process(self, notification):
        emails = []
        if notification.user:
            user = notification.user
            if user.email:
                emails.append(user.email)
            try:
                personal_email = user.docbowprofile.personal_email
                if personal_email:
                    emails.append(personal_email)
            except models.DocbowProfile.DoesNotExist:
                pass
        if not emails and notification.ctx and 'to' in notification.ctx:
            emails.extend(notification.ctx['to'])
        if not emails:
            return
        headers = {}
        if notification.ctx and 'reply_to' in notification.ctx:
            headers['Reply-To'] = notification.ctx['reply_to']
        to = set(emails)
        subject = self.generate_part(self.subject_template, notification)
        subject = subject.replace('\n', '').replace('\r', '')
        body = self.generate_part(self.body_template, notification)
        html_body = self.generate_part(self.html_body_template, notification)
        mail = EmailMultiAlternatives(to=list(to), subject=subject, body=body,
                headers=headers)
        if html_body:
            mail.attach_alternative(html_body, 'text/html')
        mail.send(fail_silently=False)
        django_journal.record('mail-notify', 'mail notification {notification} '
                'sent for document {document} to {to} of user {recipient}',
                notification=notification,
                recipient=notification.user,
                document=notification.document,
                to=','.join(to))


class SMSNotifier(BaseNotifier):
    '''Variable in sms notification:
         - document
         - settings
    '''
    body_template = 'docbow/sms-notification_{kind}_body.txt'

    def __init__(self):
        self.mobile_phones = dict()

    def process(self, notification):
        try:
            profile = models.DocbowProfile.objects.get(user=notification.user)
        except models.DocbowProfile.DoesNotExist:
            return
        if not profile.mobile_phone:
            return
        self.mobile_phones.setdefault((notification.document, notification.kind), []) \
                .append((notification.user, profile.mobile_phone))

    @classmethod
    def get_carrier(cls):
        return resolve_class(settings.DOCBOW_SMS_CARRIER_CLASS)()

    def finish(self):
        if not self.mobile_phones:
            return
        exc = None
        for key, value in self.mobile_phones.iteritems():
            try:
                document, kind = key
                notification = models.Notification(document=document, kind=kind)
                body = self.generate_part(self.body_template, notification)
                sms_carrier = self.get_carrier()
                for user, phone_number in value:
                    django_journal.record('sms-notify',
                            'sms notification for document {document} to user '
                            '{recipient} with phone number {phone_number}',
                            document=document, recipient=user,
                            phone_number=phone_number)
                sms_carrier.send_sms([v[1] for v in value], body)
            except Exception, e:
                exc = e
        if exc:
            raise exc



def resolve_class(class_path):
    module, cls_name = class_path.rsplit('.', 1)
    module = import_module(module)
    return getattr(module, cls_name)


def get_notifiers():
    notification_classes = getattr(settings, 'DOCBOW_NOTIFIERS', [
        'docbow_project.docbow.notification.MailNotifier' ])
    return [resolve_class(class_path)() for class_path in notification_classes]


def process_notifications():
    notifiers = get_notifiers()
    for notification in models.Notification.objects.select_for_update().filter(done=False):
        for notifier in notifiers:
            failures = []
            try:
                notifier.process(notification)
            except Exception, e:
                failures.append(u'Exception %r when handling with notifier %r'
                        % (unicode(e), notifier.__class__))
                logger.exception('Exception when handling notification %r with notifier %r',
                        notification, notifier)
                django_journal.error_record('error',
                        'notification {notification} failed for '
                        'notifier {notifier}',
                        notification=notification,
                        notifier=notifier.__class__)
            notification.done = True
            if len(failures) > 0:
                notification.failure = ','.join(failures)
            notification.save()
    for notifier in notifiers:
        try:
            notifier.finish()
        except Exception, e:
            logger.exception('Exception when finishing handling '
                'notification %r with notifier %r', notification, notifier)
            django_journal.error_record('error', 'unable to finish sending '
                    'notification with notifier {notifier}, error: {error}',
                    notifier=notifier.__class__,
                    error=unicode(e))
