from optparse import make_option
import sys
import email
import email.errors
import email.utils
import email.header
import logging
import re

from django.core.management.base import BaseCommand, CommandError
import django.contrib.auth.models as auth_models
from django.core.files.base import ContentFile
from django.db import transaction
from django.core.exceptions import MultipleObjectsReturned

from docbow_project.docbow.app_settings import BASE_URL

from ... import models, views, timestamp
from ...email_utils import u2u_decode

logger = logging.getLogger('docbow.mail_interface')

class Command(BaseCommand):
    args = ''
    help = '''Convert a mail to a document send.
    
In case of failure the following return value is returned:
 - 1 failure to parse the message on stdin
 - 2 the mail is missing a subject
 - 3 the subject could not be decoded
 - 4 obligatory attributes or contents were missing
 - 5 notifications mails could not be sent
 - 6 missing Message-ID
'''

    option_list = BaseCommand.option_list + (
            make_option("--sender"),
            make_option("--base-url", default=BASE_URL),
            make_option("--ip", default=''),
            make_option("--file"))

    def handle(self, *args, **options):
        mail_sender = None
        self.logger = logging.LoggerAdapter(logging.getLogger('docbow'), {
            'ip': options['ip'], 'user': 'Anonymous' })
        if options.get('sender'):
            try:
                mail_sender = auth_models.User.objects.get(username=options['sender'])
            except auth_models.User.DoesNotExist:
                raise CommandError('Not found')

        try:
            if options.get('file'):
                mail = email.message_from_file(file(options['file']))
            else:
                mail = email.message_from_file(sys.stdin)
        except email.errors.MessageParseError, e:
            self.error('7.7.1 Error parsing message', exite_code=1)
        try:
            self.handle_mail(mail, mail_sender, args, **options)
        except Exception, e:
            self.logger.exception('Unknown exception')
            self.error('7.7.1 Uknown error when handling the mail', exit_code=5)

    def error(self, msg, exit_code=None, *args):
        if args:
            print >>sys.stderr, msg % args
        else:
            print >>sys.stderr, msg
        if hasattr(self, 'message_id'):
            msg = ('smtp interface: message %s refused, ' % self.message_id) + msg
        else:
            msg = 'smtp interface: message refused, ' + msg
        self.logger.error(msg, *args)
        if exit_code:
            sys.exit(exit_code)

    def decode_filename(self, filename):
        '''See if the filename contains encoded-word work around bugs in FileMakerPro'''
        m = re.match(r'=\?(.*)\?(.*)\?(.*)\?=', filename)
        if m:
            result = []
            for content, encoding in email.header.decode_header(filename):
                result.append(unicode(content, encoding or 'ascii'))
            return ''.join(result)
        else:
            return filename

    def handle_mail(self, mail, mail_sender, mail_recipients, **options):
        content_errors = []
        attachments = []
        recipients = []
        description = u''
        mail_from = email.utils.parseaddr(mail.get('From'))[1]
        tos = mail.get_all('to', [])
        ccs = mail.get_all('cc', [])
        resent_tos = mail.get_all('resent-to', [])
        resent_ccs = mail.get_all('resent-cc', [])
        all_recipients = mail_recipients or [b for a, b in email.utils.getaddresses(tos + ccs + resent_tos +
                resent_ccs)]
        self.message_id = mail.get('Message-ID', None)
        if not self.message_id:
            self.error('7.7.1 Mail is missing a Message-ID', exit_code=6)
        subject = mail.get('Subject', None)
        if not subject:
            self.error('7.7.1 Mail is missing a subject', exit_code=2)
        try:
            subject = u2u_decode(subject)
        except Exception, e:
            self.error('7.7.1 The subject cannot be decoded', exit_code=3)
        try:
            filetype = models.FileType.objects.get(name=subject)
        except models.FileType.DoesNotExist:
            content_errors.append('The subject "%s" does not match any known file type' % subject)
        for part in mail.walk():
            filename = part.get_filename(None)
            if part.get_content_type() == 'text/plain' and \
                ('Content-Disposition' not in part or 'inline' in part['Content-Disposition']):
                charset = part.get_content_charset('us-ascii')
                description = unicode(part.get_payload(decode=True), charset)

            if filename:
                attachments.append((self.decode_filename(filename),
                    part.get_payload(decode=True)))
        for email_address in all_recipients:
            try:
                user = auth_models.User.objects.get(email=email_address, delegations_by__isnull=True)
                recipients.append(user)
            except auth_models.User.DoesNotExist:
                msg = 'Recipient %r is not an user of the platform' \
                        % email_address
                content_errors.append(msg)
            except MultipleObjectsReturned:
                msg = 'Recipient %r has more than 1 user in the platform' \
                        % email_address
                content_errors.append(msg)
        if not len(attachments):
            content_errors.append('You must sent at least one attached file')
        if not len(all_recipients):
            content_errors.append('You must have at least one recipient in your message.')
        try:
            sender = mail_sender or auth_models.User.objects.get(email=mail_from)
        except auth_models.User.DoesNotExist:
            content_errors.append('Unable to find an unique sender for the mail %s' % mail.get('From'))
        if content_errors:
            msg = [ '7.7.1 The email sent contains many errors:' ]
            for error in content_errors:
                msg.append(' - %s' % error)
            self.error('\n'.join(msg), exit_code=4)
        else:
            with transaction.commit_on_success():
                document = models.Document(sender=sender,
                        comment=description, filetype=filetype)
                document.save()
                document.to_user = recipients
                for filename, payload in attachments:
                    content = ContentFile(payload)
                    attached_file = models.AttachedFile(document=document,
                            name=filename)
                    attached_file.content.save(filename, content, save=False)
                    attached_file.save()
                document.post()
                if not views.send_mail_notifications(document, options['base_url'], logger=self.logger):
                    transaction.rollback()
                    self.error('7.7.1 Document send failed because '
                        'some notifications could not be sent, administrators '
                        'have been informed.', code=5)
                else:
                    blob = document.timestamp_blob()
                    tst = timestamp.timestamp_json(blob)
                    self.logger.info('smtp interface: message %s accepted, sent %s, timestamp %s' % (self.message_id, document, tst))
