"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".

Replace this with more appropriate tests for your application.
"""

from io import StringIO
import tempfile
import sys
from contextlib import contextmanager
from functools import wraps

from django.utils.encoding import force_bytes, force_text


import mock

MEDIA_ROOT = tempfile.mkdtemp()


@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err


class stderr_output(object):
    def __init__(self, output):
        self.output = output

    def __call__(self, func):
        @wraps(func)
        def f(testcase, *args, **kwargs):
            with captured_output() as (out, err):
                ret = func(testcase, *args, **kwargs)
            testcase.assertEqual(self.output, err.getvalue())
            return ret

        return f


class stdout_output(object):
    def __init__(self, output):
        self.output = output

    def __call__(self, func):
        @wraps(func)
        def f(testcase, *args, **kwargs):
            with captured_output() as (out, err):
                ret = func(testcase, *args, **kwargs)
            testcase.assertEqual(self.output, out.getvalue())
            return ret

        return f


from django.test import TestCase
from django.test.utils import override_settings
from django.core import management
from django.contrib.auth.models import User

from docbow_project.pfwb.models import TabellioDocType, PloneFileType
from docbow_project.docbow.models import FileType, MailingList, Document, AttachedFile

from django_journal.models import Journal

EXPEDITION_EMAIL = 'expedition@example.com'
RECIPIENT_EMAIL = 'recipient@example.com'
RECIPIENT_LIST_EMAIL = 'liste-ma-liste@example.com'


class MockUrlib(object):
    def urlopen(self, url):
        return self

    def read(self):
        with open('tests/data/django.pdf', 'rb') as f:
            return f.read()


mockurllib = MockUrlib()


@override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_ID=1)
@override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_NAME='Default')
@override_settings(DOCBOW_PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL=EXPEDITION_EMAIL)
@override_settings(DOCBOW_PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID=1)
@override_settings(DOCBOW_PFWB_GED_DIRECTORY=None)
@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class SendMailTestCase(TestCase):
    def setUp(self):
        self.pjd_filetype = FileType.objects.create(name='PJD', id=2)
        self.tabellio_doc_type = TabellioDocType.objects.create(
            filetype=self.pjd_filetype, tabellio_doc_type='PJD'
        )
        self.expedition_user = User.objects.create(username='expedition', id=1)
        self.to_user = User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2)
        self.to_list = MailingList.objects.create(name='ma liste')
        self.to_list.members.add(self.to_user)

    def send_tabellio_doc2(self, content, expedition_email, recipient_email, doc_type, subject):
        content = content % {
            'expedition_email': expedition_email,
            'recipient_email': recipient_email,
            'doc_type': doc_type,
            'subject': subject,
        }
        with tempfile.NamedTemporaryFile() as f:
            f.write(force_bytes(content))
            f.flush()
            management.call_command('sendmail', recipient_email, file=f.name, sender=expedition_email)

    def send_tabellio_doc(self, expedition_email, recipient_email, doc_type, subject):
        content = '''\
Message-ID: <232323232@example.com>
From: %(expedition_email)s
To: %(recipient_email)s
Subject: %(subject)s
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.20 (2009-06-14)
X-Tabellio-Doc-Type: %(doc_type)s
X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf
Status: RO

Coucou
'''
        self.send_tabellio_doc2(content, expedition_email, recipient_email, doc_type, subject)

    @stderr_output('7.7.1 No sender\n7.7.1 No sender\n')
    def test_fail_sender(self):
        with self.assertRaises(SystemExit):
            management.call_command('sendmail', 'yyy')
        with self.assertRaises(SystemExit):
            management.call_command('sendmail', 'yyy', sender='')

    @stderr_output('7.7.1 Mail is missing a Message-ID\n')
    def test_fail_on_missing_message_id(self):
        with self.assertRaises(SystemExit):
            self.send_tabellio_doc2(
                '''From: %(expedition_email)s
To: %(recipient_email)s
Subject: %(subject)s
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.20 (2009-06-14)
X-Tabellio-Doc-Type: %(doc_type)s
X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf
Status: RO

Coucou''',
                EXPEDITION_EMAIL,
                RECIPIENT_EMAIL,
                'PJD',
                'Mouais: monfichier.pdf',
            )

    @stderr_output('7.7.1 Mail is missing a subject\n')
    def test_fail_on_missing_subject(self):
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            with self.assertRaises(SystemExit):
                self.send_tabellio_doc2(
                    '''From: %(expedition_email)s
Message-ID: <232323232@example.com>
To: %(recipient_email)s
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.20 (2009-06-14)
X-Tabellio-Doc-Type: %(doc_type)s
X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf
Status: RO

Coucou''',
                    EXPEDITION_EMAIL,
                    RECIPIENT_EMAIL,
                    'PJD',
                    'Mouais: monfichier.pdf',
                )

    @stderr_output('7.7.1 Filename cannot be extracted from the subject\n')
    def test_fail_on_missing_colon_in_subject(self):
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            with self.assertRaises(SystemExit):
                self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais')

    @stderr_output(
        "7.7.1 The email sent contains many errors:\n - Recipient 'xxx@xx.com' is not an user of the platform\n"
    )
    def test_fail_on_unknown_user(self):
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            with self.assertRaises(SystemExit):
                self.send_tabellio_doc(EXPEDITION_EMAIL, 'xxx@xx.com', 'PJD', 'Mouais: monfichier.pdf')

    @stderr_output('')
    def test_fail_unknown_file_type(self):
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            self.assertEqual(FileType.objects.filter(name='Default').count(), 0)
            self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'XXX', 'Mouais: monfichier.pdf')
            self.assertEqual(
                Journal.objects.order_by('id')[0].message,
                'unknown x-tabellio-doc-type XXX, using default filetype',
            )
            self.assertEqual(FileType.objects.filter(name='Default').count(), 1)

    @stderr_output('')
    def test_expedition_mode(self):
        """
        Test sendmail management command when using the expedition mode
        """
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf')
            self.assertEqual(Document.objects.count(), 1)
            assert Document.objects.get().to_user.count() == 1
            assert Document.objects.get().to_list.count() == 0

    @stderr_output('')
    def test_get_user_by_email(self):
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            self.to_user.username = 'xyzfhtkre'
            self.to_user.save()
            self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf')
            self.assertEqual(Document.objects.count(), 1)
            assert Document.objects.get().to_user.count() == 1
            assert Document.objects.get().to_list.count() == 0

    @stderr_output('')
    def test_expedition_mode_to_list(self):
        """
        Test sendmail management command when using the expedition mode
        """
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_LIST_EMAIL, 'PJD', 'Mouais: monfichier.pdf')
            self.assertEqual(Document.objects.count(), 1)
            assert Document.objects.get().to_user.count() == 0
            assert Document.objects.get().to_list.count() == 1
            assert Document.objects.get().to_list.all()[0] == self.to_list


@override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_ID=1)
@override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_NAME='Default')
@override_settings(DOCBOW_PFWB_SENDMAIL_ATTACHED_FILE_EMAIL=EXPEDITION_EMAIL)
@override_settings(DOCBOW_PFWB_SENDMAIL_ATTACHED_FILE_USER_ID=1)
@override_settings(DOCBOW_PFWB_GED_DIRECTORY=None)
@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class SendMailAttachedFileTestCase(TestCase):
    def setUp(self):
        self.pjd_filetype = FileType.objects.create(name='PJD', id=2)
        self.tabellio_doc_type = TabellioDocType.objects.create(
            filetype=self.pjd_filetype, tabellio_doc_type='PJD'
        )
        self.expedition_user = User.objects.create(username='expedition', id=1)
        self.to_user = User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2)
        self.to_list = MailingList.objects.create(name='ma liste')
        self.to_list.members.add(self.to_user)

    def build_message(self, filetype, to_addr, from_addr, content, attached_files):
        from email import encoders
        from email.header import Header
        from email.mime.base import MIMEBase
        from email.mime.multipart import MIMEMultipart
        from email.mime.text import MIMEText
        import random

        message = MIMEMultipart()
        message['Subject'] = Header(force_text(filetype), 'utf-8')
        message['To'] = Header(to_addr, 'utf-8')
        message['From'] = Header(from_addr, 'utf-8')
        message['Message-ID'] = '<%s@example.com>' % random.random()
        msg = MIMEText(content, _subtype='plain')
        message.attach(msg)
        for filename, content in attached_files:
            msg = MIMEBase('application', 'octet-stream')
            msg.set_payload(content)
            msg.add_header('Content-Disposition', 'attachment', filename=filename)
            encoders.encode_base64(msg)
            message.attach(msg)
        return force_bytes(message.as_string())

    @stderr_output('')
    def test_attached_file1(self):
        with tempfile.NamedTemporaryFile() as f:
            f.write(
                self.build_message(
                    self.pjd_filetype,
                    EXPEDITION_EMAIL,
                    RECIPIENT_EMAIL,
                    'coucou',
                    (('attached-file', 'content'),),
                )
            )
            f.flush()
            management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, sender=EXPEDITION_EMAIL)
            self.assertEqual(Document.objects.count(), 1)
            document = Document.objects.get()
            assert document.attached_files.count() == 1
            assert document.comment == 'coucou'
            assert document.attached_files.get().name == 'attached-file'
            assert document.attached_files.get().content.read() == b'content'
            assert document.to_user.count() == 1
            assert document.to_list.count() == 0
            assert document.to_user.get() == self.to_user


@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class PushDocumentTestCase(TestCase):
    def setUp(self):
        self.ged_dir = tempfile.mkdtemp()

    def tearDown(self):
        import shutil

        shutil.rmtree(self.ged_dir)

    def test_push_document1(self):
        from django.core.files.base import ContentFile
        import os.path
        from glob import glob

        with self.settings(DOCBOW_PFWB_GED_DIRECTORY=self.ged_dir):
            FROM_USERNAME = 'from_user'
            FROM_FIRST_NAME = 'from_first_name'
            FROM_LAST_NAME = 'from_last_name'
            self.from_user = User.objects.create(
                username=FROM_USERNAME, first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME
            )
            self.to_user = User.objects.create(username='to_user')
            self.filetype = FileType.objects.create(name='filetype')
            self.plone_filetype = PloneFileType.objects.create(
                filetype=self.filetype, plone_portal_type='plone-portal-type'
            )
            DESCRIPTION = 'description'
            self.document = Document.objects.create(
                sender=self.from_user, filetype=self.filetype, comment=DESCRIPTION
            )
            self.attached_file = AttachedFile(name='attached-file', document=self.document, kind=None)
            CONTENT = 'content'
            self.attached_file.content.save('attached-file', ContentFile(CONTENT))
            pattern1 = '{0}-*-{1}.json'.format(self.document.id, self.attached_file.name)
            pattern2 = '{0}-*-{1}'.format(self.document.id, self.attached_file.name)
            files1 = glob(os.path.join(self.ged_dir, pattern1))
            files2 = glob(os.path.join(self.ged_dir, pattern2))
            assert len(files1) == 1
            assert len(files2) == 1
            with open(files2[0]) as f:
                assert f.read() == CONTENT
            import json

            with open(files1[0]) as f:
                json_content = json.loads(f.read())
                self.assertIsNotNone(json_content)
                assert json_content['document_id'] == self.document.id
                assert json_content['plone_portal_type'] == self.plone_filetype.plone_portal_type
                assert json_content['title'] == force_text(self.filetype)
                assert json_content['description'] == DESCRIPTION
                assert json_content['sender'] == u'{0} {1} ({2})'.format(
                    FROM_FIRST_NAME, FROM_LAST_NAME, FROM_USERNAME
                )


@override_settings(MEDIA_ROOT=MEDIA_ROOT)
@override_settings(DOCBOW_PFWB_GED_DIRECTORY=None)
class ArchiveTestCase(TestCase):
    def setUp(self):
        from django.core.files.base import ContentFile

        self.archive_dir = tempfile.mkdtemp()
        FROM_USERNAME = 'from_user'
        FROM_FIRST_NAME = 'from_first_name'
        FROM_LAST_NAME = 'from_last_name'
        self.from_user = User.objects.create(
            username=FROM_USERNAME, first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME
        )
        self.to_user = User.objects.create(username='to_user')
        self.filetype = FileType.objects.create(name='filetype')
        self.plone_filetype = PloneFileType.objects.create(
            filetype=self.filetype, plone_portal_type='plone-portal-type'
        )
        DESCRIPTION = 'description'
        self.document = Document.objects.create(
            sender=self.from_user, filetype=self.filetype, comment=DESCRIPTION
        )
        self.attached_file = AttachedFile(name='attached-file', document=self.document, kind=None)
        CONTENT = 'content'
        self.attached_file.content.save('attached-file', ContentFile(CONTENT))
        self.document.post()

    def tearDown(self):
        import shutil

        shutil.rmtree(self.archive_dir)

    def test_archive(self):
        import os.path
        import glob
        import datetime

        with captured_output() as (out, err):
            management.call_command('archive2', self.archive_dir, 0)
        l = glob.glob(os.path.join(self.archive_dir, '*'))
        assert len(l) == 1
        self.assertTrue(l[0].split('T')[0], datetime.datetime.today().isoformat())
        archive_dir = os.path.join(self.archive_dir, l[0])
        self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc')))
        self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc', str(self.document.id))))
        self.assertTrue(
            os.path.exists(os.path.join(archive_dir, 'doc', str(self.document.id), 'document.json'))
        )
        self.assertTrue(
            os.path.exists(
                os.path.join(
                    archive_dir, 'doc', str(self.document.id), 'attached_file_%s.json' % self.attached_file.id
                )
            )
        )
        self.assertTrue(os.path.exists(os.path.join(archive_dir, 'journal.txt')))


def test_send_mail_truncate_filename(db, settings):
    settings.DOCBOW_PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL = EXPEDITION_EMAIL
    settings.DOCBOW_PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID = 1
    settings.MEDIA_ROOT = MEDIA_ROOT

    pjd_filetype = FileType.objects.create(name='PJD', id=2)
    tabellio_doc_type = TabellioDocType.objects.create(filetype=pjd_filetype, tabellio_doc_type='PJD')
    expedition_user = User.objects.create(username='expedition', id=1)
    to_user = User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2)

    subject = 'foo: ' + 'aaaa M.' + 'b' * 300
    content = '''\
Message-ID: <232323232@example.com>
From: %(expedition_email)s
To: %(recipient_email)s
Subject: foo: %(subject)s
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.20 (2009-06-14)
X-Tabellio-Doc-Type: %(doc_type)s
X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf
Status: RO
Coucou
''' % {
        'expedition_email': EXPEDITION_EMAIL,
        'recipient_email': RECIPIENT_EMAIL,
        'doc_type': 'PJD',
        'subject': subject,
    }
    with tempfile.NamedTemporaryFile() as f:
        f.write(force_bytes(content))
        f.flush()
        with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen):
            management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, sender=EXPEDITION_EMAIL)

    assert Document.objects.count() == 1
