# -*- coding: utf-8 -*-
# Copyright (C) 2017-2021 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
# Postorius is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Postorius is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Postorius.  If not, see <http://www.gnu.org/licenses/>.
#
import unittest

from django.test import TestCase

from postorius.forms.list_forms import (
    ArchiveSettingsForm, ChangeSubscriptionForm, DigestSettingsForm,
    DMARCMitigationsForm, ListAnonymousSubscribe, ListAutomaticResponsesForm,
    ListHeaderMatchForm, ListIdentityForm, ListMassRemoval,
    ListMassSubscription, ListNew, ListSubscribe, MemberModeration,
    MemberPolicyForm, MessageAcceptanceForm)
from postorius.forms.system import AddBanForm
from postorius.tests.utils import create_mock_list


class TestListSubscribe(TestCase):

    def test_required_fields_only(self):
        user_emails = ['bob@example.com', 'anne@example.com']
        form = ListSubscribe(user_emails, None, None,
                             dict(subscriber='bob@example.com'))
        self.assertTrue(form.is_valid())

    def test_email_is_only_from_choices(self):
        user_emails = ['bob@example.com', 'anne@example.com']
        form = ListSubscribe(user_emails, None, None,
                             dict(subscriber='alice@example.com',
                                  display_name='Alice'))
        self.assertFalse(form.is_valid())
        self.assertTrue('subscriber' in form.errors)
        self.assertTrue('Select a valid choice.' in
                        form.errors['subscriber'][0])

    def test_subscribe_works(self):
        user_emails = ['someone@example.com']
        form = ListSubscribe(user_emails, None, None,
                             {'subscriber': 'someone@example.com',
                              'display_name': 'Someone'})
        self.assertTrue(form.is_valid())

    def test_subscribe_fails(self):
        user_emails = ['someone@example.com']
        form = ListSubscribe(user_emails, None, None,
                             {'subscriber': 'notaemail',
                              'display_name': 'Someone'})
        self.assertFalse(form.is_valid())
        self.assertTrue('subscriber' in form.errors.keys())
        self.assertEqual(form.errors['subscriber'][0],
                         'Select a valid choice.'
                         ' notaemail is not one of the available choices.')

    def test_subscribe_validates_email(self):
        user_emails = ['something']
        form = ListSubscribe(user_emails, None, None,
                             {'subscriber': 'something',
                              'display_name': 'Someone'})
        self.assertFalse(form.is_valid())
        self.assertTrue('subscriber' in form.errors.keys())
        self.assertEqual(form.errors['subscriber'][0],
                         'Please enter a valid email address.')

    def test_subscribe_with_user_uuid(self):
        user_emails = ['aperson@example.com']
        form = ListSubscribe(user_emails,
                             '00000000000000000000000000000004',
                             'aperson@example.com',
                             {'subscriber': '00000000000000000000000000000004'}
                             )
        self.assertEqual(form.is_valid(), True)

    def test_subscribe_with_invalid_uuid(self):
        # Test that invalid UUID returns errors.
        user_emails = ['aperson@example.com']
        form = ListSubscribe(user_emails,
                             '4',
                             'aperson@example.com',
                             {'subscriber': '4'})
        self.assertEqual(form.is_valid(), False)
        self.assertEqual(form.errors['subscriber'][0],
                         'Please enter a valid email address.')

    def test_list_subscribe_with_delivery_mode(self):
        user_emails = ['aperson@example.com']
        form = ListSubscribe(user_emails,
                             '00000000000000000000000000000004',
                             'aperson@example.com',
                             {'subscriber': '00000000000000000000000000000004',
                              'delivery_mode': 'plaintext_digests',
                              'delivery_status': 'by_user'})
        self.assertTrue(form.is_valid())

    def test_list_subscribe_with_invalid_delivery_status(self):
        user_emails = ['aperson@example.com']
        form = ListSubscribe(user_emails,
                             '00000000000000000000000000000004',
                             'aperson@example.com',
                             {'subscriber': '00000000000000000000000000000004',
                              'delivery_mode': 'plaintext_digest',
                              'delivery_status': 'by_bounces'})
        self.assertFalse(form.is_valid())
        self.assertTrue('delivery_status' in form.errors)


class TestChangeSubscription(TestCase):

    def test_subscription_changes_only_to_user_addresses(self):
        user_emails = ['one@example.com', 'two@example.com']
        form = ChangeSubscriptionForm(user_emails, None, None,
                                      {'subscriber': 'abcd@d.com',
                                       'member_id': '1235sdfsd314'})
        self.assertFalse(form.is_valid())
        self.assertTrue('subscriber' in form.errors.keys())
        self.assertEqual(form.errors['subscriber'][0],
                         'Select a valid choice. '
                         'abcd@d.com is not one of the available choices.')

    def test_subscription_works(self):
        user_emails = ['one@example.com', 'two@example.com']
        form = ChangeSubscriptionForm(user_emails, None, None,
                                      {'subscriber': 'two@example.com',
                                       'member_id': '1235sdfsd314'})
        self.assertTrue(form.is_valid())

    def test_subscription_form_labels(self):
        user_emails = ['one@example.com', 'two@example.com']
        form = ChangeSubscriptionForm(user_emails, None, None,
                                      {'member_id': '1235sdfsd314'})
        self.assertTrue(form.is_valid())
        self.assertEqual(form.fields['subscriber'].label, 'Select Email')

    def test_form_validity(self):
        form = ChangeSubscriptionForm(
            ['email@example.com', 'john@example.com', 'doe@example.com'],
            None, None,
            {'subscriber': 'email@example.com',
             'member_id': '1235sdfsd314'})
        self.assertTrue(form.is_valid())

    def test_required_fields(self):
        # There is only one required fields.
        form = ChangeSubscriptionForm(
            ['email@example.com', 'john@example.com', 'doe@example.com'],
            None, None,
            {'member_id': '1235sdfsd314'})
        self.assertTrue(form.is_valid())

    def test_change_subscription_with_user_uuid(self):
        user_emails = ['aperson@example.com']
        form = ChangeSubscriptionForm(
            user_emails,
            '00000000000000000000000000000004',
            'aperson@example.com',
            {'subscriber': '00000000000000000000000000000004',
             'member_id': '1235sdfsd314'})
        self.assertEqual(form.is_valid(), True)

    def test_subscribe_with_invalid_uuid(self):
        # Test that invalid UUID returns errors.
        user_emails = ['aperson@example.com']
        # UUIDs are 32bit and 4 is not a valid email.
        form = ChangeSubscriptionForm(user_emails,
                                      '4',
                                      'aperson@example.com',
                                      {'subscriber': '4'})
        self.assertEqual(form.is_valid(), False)
        self.assertEqual(form.errors['subscriber'][0],
                         'Invalid: "4" should be either email or UUID')


class TestListNew(TestCase):

    def test_form_fields_list(self):
        domain_choices = [
            ("mailman.most-desirable.org", "mailman.most-desirable.org")]
        style_choices = [
            ("legacy-default", 'Ordinary discussion mailing list style.'),
            ("legacy-announce", 'Announce only mailing list style.')]
        form = ListNew(domain_choices, style_choices,
                       {'listname': 'xyz',
                        'mail_host': 'mailman.most-desirable.org',
                        'list_owner': 'contact@mailman.most-desirable.org',
                        'advertised': 'True',
                        'list_style': 'legacy-default',
                        'description': 'The Most Desirable organization'})
        self.assertTrue(form.is_valid(), form.errors)

    def test_form_fields_list_invalid(self):
        domain_choices = [
            ("mailman.most-desirable.org", "mailman.most-desirable.org")]
        style_choices = [
            ("legacy-default", 'Ordinary discussion mailing list style.'),
            ("legacy-announce", 'Announce only mailing list style.')]
        form = ListNew(domain_choices, style_choices,
                       {'listname': 'xy#z',
                        'mail_host': 'mailman.most-desirable.org',
                        'list_owner': 'mailman.most-desirable.org',
                        'advertised': 'abcd',
                        'list_style': 'defg',
                        'description': 'The Most Desirable organization'})
        self.assertFalse(form.is_valid())
        # Test that all invalid fields are actually checked.
        for field in ('list_owner', 'advertised'):
            self.assertTrue(field in form.errors)
        self.assertTrue('Enter a valid email address.' in
                        form.errors['list_owner'])
        self.assertTrue(
            'Select a valid choice. abcd is not one of the available choices.'
            in form.errors['advertised'])

    def test_form_without_domain_choices(self):
        form = ListNew([],
                       {'listname': 'xyz',
                        'mail_host': 'mailman.most-desirable.org',
                        'list_owner': 'contact@mailman.most-desirable.org',
                        'advertised': 'True',
                        'description': 'The Most Desirable organization', })
        # Without domain choices, the form is not going to be valid.
        self.assertFalse(form.is_valid())
        self.assertTrue(form.fields['mail_host'].help_text ==     # noqa: W504
                        'Site admin has not created any domains')

    def test_listname_validation(self):
        domain_choices = [
            ("mailman.most-desirable.org", "mailman.most-desirable.org")]
        style_choices = [
            ("legacy-default", 'Ordinary discussion mailing list style.'),
            ("legacy-announce", 'Announce only mailing list style.')]
        form = ListNew(domain_choices, style_choices,
                       {'listname': 'xy@z',
                        'mail_host': 'mailman.most-desirable.org',
                        'list_owner': 'mailman.most-desirable.org',
                        'advertised': 'abcd',
                        'description': 'The Most Desirable organization', })
        self.assertFalse(form.is_valid())
        self.assertTrue('listname' in form.errors)
        self.assertTrue('Please enter a valid listname' in
                        form.errors['listname'])

    @unittest.expectedFailure
    def test_listname_validation_errors_sane(self):
        # This test is going to fail right now, but needs to be fixed.
        form = ListNew([
            ("mailman.most-desirable.org", "mailman.most-desirable.org")],
            {'listname': 'xy#z',
             'mail_host': 'mailman.most-desirable.org',
             'list_owner': 'mailman.most-desirable.org',
             'advertised': 'abcd',
             'description': 'The Most Desirable organization', })
        self.assertFalse(form.is_valid())
        self.assertTrue('listname' in form.errors)
        self.assertEqual(
            'Please enter a valid listname, "@" is not allowed in listname',
            form.errors['listname'])

    def test_form_fields_order(self):
        domain_choices = [
            ("mailman.most-desirable.org", "mailman.most-desirable.org")]
        style_choices = [
            ("legacy-default", 'Ordinary discussion mailing list style.'),
            ("legacy-announce", 'Announce only mailing list style.')]
        form = ListNew(domain_choices, style_choices,
                       {'listname': 'xyz',
                        'mail_host': 'mailman.most-desirable.org',
                        'list_owner': 'mailman@most-desirable.org',
                        'list_style': 'legacy-default',
                        'advertised': 'True',
                        'description': 'The Most Desirable organization', })
        self.assertTrue(form.is_valid())
        # The order of the fields should remain exactly like this.
        self.assertEqual(list(form.fields),
                         ['listname',
                          'mail_host',
                          'list_owner',
                          'advertised',
                          'list_style',
                          'description'])


class TestListIdentityForm(TestCase):

    def test_not_required_fields(self):
        # Only advertised is the required form field.
        form = ListIdentityForm({
            'advertised': 'True',
        }, mlist=None)
        self.assertTrue(form.is_valid(), form.errors)

    def test_field_validations(self):
        form = ListIdentityForm({
            'advertised': 'abcd',
            'description': 'This is the most desirable organization',
            'info': 'This is a larger description of this mailing list.',
            'display_name': 'Most Desirable Mailing List',
            'subject_prefix': '  [Most Desirable]               ',
            'preferred_language': 'en',
            'member_roster_visibility': 'public',
        }, mlist=None)
        self.assertFalse(form.is_valid())
        self.assertTrue('advertised' in form.errors)
        self.assertEqual(['Select a valid choice. abcd is not one of the available choices.'],  # noqa
                         form.errors['advertised'])
        # We shouldn't be removing trailing whitespaces, but we
        # should remove the leading ones.
        self.assertEqual(form.cleaned_data['subject_prefix'],
                         '[Most Desirable]               ')


class TestListMassSubscription(TestCase):

    def test_all_valid_email_formats(self):
        form = ListMassSubscription({
            'emails': '''
jdoe@example.com
<jdoe@example.com>
John Doe <jdoe@example.com>
"John Doe" <jdoe@example.com>
jdoe@example.com (John Doe)'''})
        self.assertTrue(form.is_valid())

    def test_required_fields(self):
        form = ListMassSubscription({'email': '   '})
        self.assertFalse(form.is_valid())
        self.assertEqual(['This field is required.'],
                         form.errors['emails'])
        form = ListMassSubscription({'email': '----'})
        self.assertFalse(form.is_valid())
        self.assertEqual(['This field is required.'],
                         form.errors['emails'])


class TestListMassRemoval(TestCase):

    def test_all_valid_formats(self):
        form = ListMassRemoval({
            'emails': '''
jdoe@example.com
<jdoe@example.com>
John Doe <jdoe@example.com>
"John Doe" <jdoe@example.com>
jdoe@example.com (John Doe)'''})
        self.assertTrue(form.is_valid())


class TestAddBanForm(TestCase):

    def test_form_validity(self):
        form = AddBanForm({'email': 'jdoe@example.com'})
        self.assertTrue(form.is_valid())

    def test_missing_fields_errors(self):
        form = AddBanForm({})
        self.assertFalse(form.is_valid())
        self.assertTrue('email' in form.errors)
        self.assertEqual(form.errors['email'],
                         ['Please enter an email address.'])

    @unittest.expectedFailure
    def test_invalid_fields_type(self):
        # Valid values for email is either a regexp or an email address.
        # However, this is currently not validated by the form.
        form = AddBanForm({'email': 'invalid@'})
        self.assertFalse(form.is_valid())
        self.assertTrue('email' in form.errors)
        self.assertEqual(form.errors['email'],
                         ['Please enter a valid email address.'])


class TestListHeaderMatchForm(TestCase):

    def test_form_validity(self):
        # Test by putting only required fields.
        form = ListHeaderMatchForm({
            'header': 'To',
            'pattern': 'value@example.com'})
        self.assertTrue(form.is_valid())
        # Test all fileds.
        form = ListHeaderMatchForm({
            'header': 'To',
            'pattern': 'value@example.com',
            'action': 'accept'})
        self.assertTrue(form.is_valid())
        # Defer is not a valid action, so validation should fail here.
        form = ListHeaderMatchForm({
            'header': 'To',
            'pattern': 'value@example.com',
            'action': 'defer'})
        self.assertFalse(form.is_valid())
        self.assertTrue('action' in form.errors)


class TestMemberModeration(TestCase):

    def test_moderation_action_validity(self):
        form = MemberModeration({'moderation_action': 'moderation'})
        self.assertFalse(form.is_valid())
        self.assertTrue('moderation_action' in form.errors)
        self.assertEqual(form.errors['moderation_action'],
                         ['Select a valid choice. moderation is not one of the available choices.'])    # noqa

    def test_required_fields(self):
        form = MemberModeration({})
        self.assertTrue(form.is_valid())


class TestListAutomaticResponsesForm(TestCase):

    fields = ('autorespond_owner', 'autoresponse_owner_text',
              'autorespond_postings', 'autoresponse_postings_text',
              'autorespond_requests', 'autoresponse_request_text',
              'autoresponse_grace_period', 'send_welcome_message',
              'welcome_message_uri', 'send_goodbye_message',
              'goodbye_message_uri', 'admin_immed_notify',
              'admin_notify_mchanges')

    def prepare_formdata(self, values):
        return dict(((key, val) for key, val in zip(self.fields, values) if val is not None))   # noqa

    def test_required_fields_only(self):
        values = ('respond_and_continue', None,
                  'respond_and_continue', None,
                  'respond_and_continue', None,
                  '2', None,
                  None, None,
                  None, None)
        formdata = self.prepare_formdata(values)
        form = ListAutomaticResponsesForm(formdata, mlist=None)
        self.assertTrue(form.is_valid())

    def test_all_values(self):
        values = ('respond_and_continue', 'Autorespond text',
                  'respond_and_continue', 'Autorespond text',
                  'respond_and_continue', 'Autorespond text',
                  '2', 'True',
                  'http://example.com/welcome_text',
                  'True',
                  'http://example.com/goodbye_message',
                  'True', 'False')
        formdata = self.prepare_formdata(values)
        form = ListAutomaticResponsesForm(formdata, mlist=None)
        print(form.errors)
        self.assertTrue(form.is_valid())


class TestAlterMessageForm(TestCase):

    fields = ('filter_content', 'collapse_alternatives',
              'convert_html_to_plaintext', 'anonymous_list',
              'include_rfc2369_headers', 'allow_list_posts',
              'reply_list_posts', 'reply_to_address',
              'first_strip_reply_to', 'reply_goes_to_list',
              'posting_pipeline')

    def prepare_formdata(self, values):
        return dict(((key, val) for key, val in zip(self.fields, values) if val is not None))   # noqa


class TestDMARCMitigationsForm(TestCase):

    def test_required_fields(self):
        # All fields in the form are optional, so an empty form should be
        # valid.
        form = DMARCMitigationsForm({}, mlist=None)
        self.assertTrue(form.is_valid())

    def test_all_fields(self):
        formdata = dict(
            dmarc_mitigate_action='munge_from',
            dmarc_mitigate_unconditionally='True',
            dmarc_moderation_notice='This is a moderation notice',
            dmarc_wrapped_message_text='This is wrapped message text')
        form = DMARCMitigationsForm(formdata, mlist=None)
        self.assertTrue(form.is_valid())


class TestDigestSettingsForm(TestCase):

    def test_required_fields(self):
        form = DigestSettingsForm({}, mlist=None)
        self.assertFalse(form.is_valid())
        self.assertTrue('digest_size_threshold' in form.errors)
        self.assertEqual(form.errors['digest_size_threshold'],
                         ['This field is required.'])
        form = DigestSettingsForm({'digest_size_threshold': 40}, mlist=None)
        self.assertTrue(form.is_valid())

    def test_all_fields(self):
        formdata = dict(
            digests_enabled='True',
            digests_send_periodic='True',
            digests_volume_frequency='daily',
            digest_size_threshold='10',
        )
        form = DigestSettingsForm(formdata, mlist=None)
        self.assertTrue(form.is_valid())


class TestMessageAcceptanceForm(TestCase):

    fields = ('acceptable_aliases', 'require_explicit_destination',
              'administrivia', 'default_member_action',
              'default_nonmember_action', 'max_message_size',
              'max_num_recipients')

    def prepare_formdata(self, values):
        return dict(((key, val) for key, val in zip(self.fields, values) if val is not None))   # noqa

    def test_required_fields(self):
        # Without any fields, form should not be valid.
        form = MessageAcceptanceForm({}, mlist=None)
        self.assertFalse(form.is_valid())
        # Now lets try with only required fields.
        values = (None, None, None, 'hold', 'hold', 40, 100)
        form = MessageAcceptanceForm(self.prepare_formdata(values), mlist=None)
        print(form.errors)
        self.assertTrue(form.is_valid())

    def test_all_fields(self):
        pass


class TestArchiveSettingsForm(TestCase):

    def setUp(self):
        self.mlist = create_mock_list()
        archivers = {'pipermail': True, 'hyperkitty': True}
        self.mlist.archivers.keys.side_effect = archivers.keys
        self.mlist.archivers.__getitem__.side_effect = archivers.__getitem__
        self.mlist.archivers.__iter__.side_effect = archivers.__iter__
        self.mlist.archivers.__contains__.side_effect = archivers.__contains__

    def test_required_fields(self):
        # First try without any fields.
        form = ArchiveSettingsForm({}, mlist=self.mlist)
        self.assertFalse(form.is_valid())
        self.assertTrue('archive_policy' in form.errors)
        # Now, with only required fields, this should be a valid form.
        form = ArchiveSettingsForm(dict(archive_policy='public'),
                                   mlist=self.mlist)
        self.assertTrue(form.is_valid())

    def test_all_fields(self):
        formdata = dict(archive_policy='public',
                        archivers=['pipermail', 'hyperkitty'])
        form = ArchiveSettingsForm(formdata, mlist=self.mlist)
        self.assertTrue(form.is_valid())

    def test_setup_archivers_populated(self):
        formdata = dict(archive_policy='public',
                        archivers=['pipermail', 'hyperkitty'])
        form = ArchiveSettingsForm(formdata, mlist=self.mlist,
                                   initial={'archivers': None})
        self.assertTrue(form.is_valid())
        self.assertEqual(form.initial['archivers'],
                         ['hyperkitty', 'pipermail'])

    def test_optional_archive_rendering_field(self):
        # Test that archive rendering mode is set only when Hyperkitty is
        # installed.
        formdata = dict(archive_policy='public',
                        archivers=['pipermail'])
        form = ArchiveSettingsForm(formdata, mlist=self.mlist,
                                   initial={'archivers': None})
        self.assertTrue(form.is_valid())
        self.assertFalse('archive_rendering_mode' in form.fields)
        # With Hyperkitty installed, the field should show up.
        with unittest.mock.patch(
                'postorius.forms.list_forms.apps.is_installed',
                return_value=True):
            formdata = dict(archive_policy='public',
                            archivers=['pipermail'])
            form = ArchiveSettingsForm(formdata, mlist=self.mlist,
                                       initial={'archivers': None})
            self.assertTrue(form.is_valid())
            self.assertTrue('archive_rendering_mode' in form.fields)


class TestMemberPolicyForm(TestCase):

    def test_required_fields(self):
        form = MemberPolicyForm({}, mlist=None)
        self.assertFalse(form.is_valid())
        self.assertTrue('subscription_policy' in form.errors)
        self.assertTrue('unsubscription_policy' in form.errors)
        form = MemberPolicyForm(
            dict(subscription_policy='confirm',
                 unsubscription_policy='confirm'),
            mlist=None)
        self.assertTrue(form.is_valid())


class TestListAnonymousSubscribe(TestCase):

    def test_required_fields_only(self):
        form = ListAnonymousSubscribe(dict(email='bob@exmaple.com'))
        self.assertTrue(form.is_valid())

    def test_email_is_validated(self):
        form = ListAnonymousSubscribe(dict(email='invalid'))
        self.assertFalse(form.is_valid())
        self.assertTrue('email' in form.errors)
        self.assertEqual(form.errors['email'],
                         ['Please enter a valid email address.'])

    def test_all_fields(self):
        form = ListAnonymousSubscribe(dict(email='bob@example.com',
                                           display_name='Bob'))
        self.assertTrue(form.is_valid())
