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

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 import models, views, timestamp
from docbow_project.docbow.email_utils import u2u_decode
from docbow_project.docbow.app_settings import BASE_URL
from django_journal import record

from ... import app_settings, models as pfwb_models

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

# modes
EXPEDITION = 'expedition'
ATTACHED_FILE = 'attached_file'

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
 - 7 sender not authorized
 - 8 missing sender
'''

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

    def handle(self, *args, **options):
        if not options.get('sender'):
            self.error('7.7.1 No sender', exit_code=8)
        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, args, **options)
        except Exception, e:
            logger.exception('Unknown exception')
            self.error('7.7.1 Internal error when handling the mail', exit_code=5)

    def error(self, msg, exit_code=None, **kwargs):
        print >>sys.stderr, msg.format(**kwargs)
        if hasattr(self, 'message_id'):
            record('error-smtp-interface', 'message {message_id} refused: ' + msg,
                    message_id=self.message_id, **kwargs)
        else:
            record('error-smtp-interface', 'message refused: ' + msg, **kwargs)
        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

    @transaction.commit_on_success
    def handle_mail(self, mail, mail_recipients, **options):
        content_errors = []
        attachments = []
        recipients = []
        description = u''
        from_email = email.utils.parseaddr(options['sender'])[1]
        if not from_email:
            self.error('7.7.1 No sender', exit_code=8)
        if from_email == app_settings.PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL:
            mode = EXPEDITION
        elif from_email == app_settings.PFWB_SENDMAIL_ATTACHED_FILE_EMAIL:
            mode = ATTACHED_FILE
        else:
            self.error('Email unknown, not authorized to post', exit_code=7)
            return
        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)

        # determine the filetype
        filetype = None
        if mode == ATTACHED_FILE:
            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:
                record('warning', 'unknown filetype '
                        '{filetype}, using default filetype',
                        filetype=subject)
        else:
            tabellio_doc_type = mail.get('x-tabellio-doc-type')
            if tabellio_doc_type:
                try:
                    filetype = models.FileType.objects.get(
                            tabelliodoctype__tabellio_doc_type=tabellio_doc_type)
                except models.FileType.DoesNotExist:
                    record('warning', 'unknown x-tabellio-doc-type '
                            '{tabellio_doc_type}, using default filetype',
                            tabellio_doc_type=tabellio_doc_type)
                except models.FileType.MultipleObjectsReturned:
                    record('warning', 'unknown x-tabellio-doc-type '
                            '{tabellio_doc_type}, using default filetype',
                            tabellio_doc_type=tabellio_doc_type)
        if filetype is None:
            try:
                filetype = models.FileType.objects.get(
                        id=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_ID)
            except models.FileType.DoesNotExist:
                filetype, created = models.FileType.objects.get_or_create(
                        name=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_NAME)

        if mode == ATTACHED_FILE:
            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)))
        else:
            url = mail.get('x-tabellio-doc-url')
            stream = urllib2.urlopen(url)
            content = stream.read()
            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:
                name = subject.split(':', 1)[1].strip()
            except IndexError:
                self.error('7.7.1 Filename cannot be extracted from the subject', exit_code=3)
            attachments.append((name, content))

        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 send at least one attached file')
        if not len(all_recipients):
            content_errors.append('You must have at least one recipient in your message.')
        try:
            if mode == ATTACHED_FILE:
                user_id = app_settings.PFWB_SENDMAIL_ATTACHED_FILE_USER_ID
            else:
                user_id = app_settings.PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID
            sender = auth_models.User.objects.get(id=user_id)
        except auth_models.User.DoesNotExist:
            content_errors.append('No user match the sender user_id %s in mode '
                    '%s' % (user_id, mode))
        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:
            if mode == ATTACHED_FILE:
                record('smtp-received-document', 'mode: {mode} message-id: {message_id} subject: {subject}',
                        mode=mode, message_id=self.message_id, subject=subject)
            else:
                record('smtp-received-document', 'mode: {mode} message-id: '
                       '{message_id} x-tabellio-doc-url: {x_tabellio_doc_url} '
                       'x-tabellio-doc-type: {x_tabellio_doc_type}',
                        mode=mode, message_id=self.message_id,
                        subject=subject, x_tabellio_doc_url=url,
                        x_tabellio_doc_type=tabellio_doc_type)
            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()
