# coding: utf-8
"""Contacts backend tests."""

import httmock

from django import forms
from django.urls import reverse
from django.utils import timezone

from modoboa.admin import factories as admin_factories
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoAPITestCase, ModoTestCase
from modoboa.parameters import forms as param_forms
from modoboa.parameters import tools as param_tools

from . import factories
from . import mocks
from . import models


class RadicaleParametersForm(param_forms.AdminParametersForm):
    """Since contacts plugin depends on radicale, we need this."""

    app = "modoboa_radicale"

    server_location = forms.URLField(
        label="Server URL",
        help_text=(
            "The URL of your Radicale server. "
            "It will be used to construct calendar URLs."
        ),
        widget=forms.TextInput(attrs={"class": "form-control"})
    )


param_tools.registry.add("global", RadicaleParametersForm, "Radicale")


class TestDataMixin(object):
    """Create some data."""

    @classmethod
    def setUpTestData(cls):
        """Create test data."""
        super(TestDataMixin, cls).setUpTestData()
        admin_factories.populate_database()
        cls.user = core_models.User.objects.get(username="user@test.com")
        cls.addressbook = cls.user.addressbook_set.first()
        cls.category = factories.CategoryFactory(user=cls.user, name="Family")
        cls.contact = factories.ContactFactory(
            addressbook=cls.addressbook, emails=["homer@simpson.com"],
            phone_numbers=["01234567889"],
        )
        factories.ContactFactory(
            addressbook=cls.addressbook,
            first_name="Marge", emails=["marge@simpson.com"],
            categories=[cls.category]
        )
        factories.ContactFactory(
            addressbook=cls.addressbook,
            first_name="Bart", emails=["bart@simpson.com"])

    def setUp(self):
        """Initiate test context."""
        self.client.force_login(self.user)
        self.set_global_parameter(
            "server_location", "http://example.test/radicale/",
            app="modoboa_radicale")

    def enable_cdav_sync(self):
        """Enable sync. for user."""
        url = reverse("core:user_preferences")
        with httmock.HTTMock(mocks.options_mock, mocks.mkcol_mock):
            response = self.client.post(
                url, {"modoboa_contacts-enable_carddav_sync": True,
                      "modoboa_contacts-sync_frequency": 300},
            )
        self.assertEqual(response.status_code, 200)


class ViewsTestCase(TestDataMixin, ModoTestCase):
    """Check views."""

    def test_user_settings(self):
        """Check that remote collection creation request is sent."""
        # 1. Addressbook with contacts must be synced manually
        data = {"username": self.user.username, "password": "toto"}
        self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        self.addressbook.refresh_from_db()
        self.assertIs(self.addressbook.last_sync, None)

        # 2. Addressbook with no contacts can be considered synced
        user = core_models.User.objects.get(username="user@test2.com")
        abook = user.addressbook_set.first()
        data = {"username": user.username, "password": "toto"}
        self.client.post(reverse("core:login"), data)
        abook.refresh_from_db()
        self.assertIs(abook.last_sync, None)
        # Now enable sync.
        self.enable_cdav_sync()
        abook.refresh_from_db()
        self.assertIsNot(abook.last_sync, None)

    def test_index(self):
        """Test index view."""
        url = reverse("modoboa_contacts:index")
        with self.assertRaises(IOError):
            response = self.client.get(url)
        # self.assertContains(response, '<div id="app">')


class AddressBookViewSetTestCase(TestDataMixin, ModoAPITestCase):
    """Address book ViewSet tests."""

    def test_default(self):
        """Test default endpoint."""
        response = self.client.get(reverse("api:addressbook-default"))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data["name"], "Contacts")

    def test_sync_to_cdav(self):
        """Test sync to CardDAV endpoint."""
        data = {"username": self.user.username, "password": "toto"}
        response = self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        with httmock.HTTMock(
                mocks.options_mock,
                mocks.mkcol_mock,
                mocks.put_mock,
                mocks.propfind_mock):
            response = self.client.get(reverse("api:addressbook-sync-to-cdav"))
        self.assertEqual(response.status_code, 200)
        abook = self.user.addressbook_set.first()
        self.assertIsNot(abook.sync_token, None)
        contact = abook.contact_set.first()
        self.assertIsNot(contact.etag, None)

    def test_sync_from_cdav(self):
        """Test sync from CardDAV endpoint."""
        data = {"username": self.user.username, "password": "toto"}
        response = self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        self.user.addressbook_set.update(last_sync=timezone.now())
        with httmock.HTTMock(
                mocks.options_mock, mocks.report_mock, mocks.get_mock):
            response = self.client.get(
                reverse("api:addressbook-sync-from-cdav"))
        self.assertEqual(response.status_code, 200)


class CategoryViewSetTestCase(TestDataMixin, ModoAPITestCase):
    """Category ViewSet tests."""

    def test_get_categories(self):
        """Check category list endpoint."""
        url = reverse("api:category-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 1)

    def test_create_category(self):
        """Create a new category."""
        url = reverse("api:category-list")
        data = {"name": u"Amitiés"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, 201)

    def test_update_category(self):
        """Update a category."""
        url = reverse("api:category-detail", args=[self.category.pk])
        data = {"name": u"Test"}
        response = self.client.put(url, data, format="json")
        self.assertEqual(response.status_code, 200)
        self.category.refresh_from_db()
        self.assertEqual(self.category.name, "Test")

    def test_delete_category(self):
        """Try to delete a category."""
        url = reverse("api:category-detail", args=[self.category.pk])
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 204)
        with self.assertRaises(models.Category.DoesNotExist):
            self.category.refresh_from_db()


class ContactViewSetTestCase(TestDataMixin, ModoAPITestCase):
    """Contact ViewSet tests."""

    def test_contact_list(self):
        """Check contact list endpoint."""
        url = reverse("api:contact-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 3)

        response = self.client.get("{}?search=homer".format(url))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 1)

    def test_filter_by_category(self):
        """Check filtering."""
        url = reverse("api:contact-list")
        response = self.client.get(
            "{}?category={}".format(url, self.category.name))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 1)

    def test_create_contact(self):
        """Create a new contact."""
        data = {"username": self.user.username, "password": "toto"}
        response = self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        self.user.addressbook_set.update(last_sync=timezone.now())
        data = {
            "first_name": "Magie", "last_name": "Simpson",
            "emails": [
                {"address": "magie@simpson.com", "type": "home"}
            ],
            "phone_numbers": [
                {"number": "+33123456789", "type": "home"}
            ]
        }
        url = reverse("api:contact-list")
        with httmock.HTTMock(mocks.options_mock, mocks.put_mock):
            response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, 201)
        contact = models.Contact.objects.get(pk=response.data["pk"])
        self.assertEqual(contact.emails.first().address, "magie@simpson.com")
        self.assertEqual(
            contact.phone_numbers.first().number,
            response.data["phone_numbers"][0]["number"])
        self.assertEqual(contact.display_name, "Magie Simpson")
        self.assertIsNot(contact.etag, None)

    def test_create_contact_quick(self):
        """Create a contact with minimal information."""
        data = {
            "emails": [
                {"address": "magie@simpson.com", "type": "home"}
            ]
        }
        url = reverse("api:contact-list")
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.data["display_name"][0],
                         "Name or display name required")

        data["display_name"] = "Magie Simpson"
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, 201)

    def test_create_contact_with_category(self):
        """Create a new contact with a category."""
        data = {
            "first_name": "Magie", "last_name": "Simpson",
            "emails": [
                {"address": "magie@simpson.com", "type": "home"}
            ],
            "categories": [self.category.pk]
        }
        url = reverse("api:contact-list")
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, 201)
        contact = models.Contact.objects.get(pk=response.data["pk"])
        self.assertEqual(contact.categories.first(), self.category)

    def test_update_contact(self):
        """Update existing contact."""
        data = {"username": self.user.username, "password": "toto"}
        self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        self.user.addressbook_set.update(last_sync=timezone.now())
        url = reverse("api:contact-detail", args=[self.contact.pk])
        email_pk = self.contact.emails.first().pk
        data = {
            "first_name": "Homer 1", "last_name": "Simpson",
            "emails": [
                {"address": "duff@simpson.com", "type": "work"},
                {"address": "homer@simpson.com", "type": "other"}
            ],
            "phone_numbers": [
                {"number": "+33123456789", "type": "home"},
                {"number": "01234567889", "type": "work"},
            ],
            "categories": [self.category.pk]
        }
        with httmock.HTTMock(
                mocks.options_mock, mocks.put_mock, mocks.report_mock):
            response = self.client.put(url, data, format="json")
        self.assertEqual(response.status_code, 200)

        self.contact.refresh_from_db()
        self.assertEqual(self.contact.first_name, "Homer 1")
        self.assertEqual(self.contact.emails.count(), 2)
        self.assertEqual(
            models.EmailAddress.objects.get(pk=email_pk).type, "other")
        self.assertEqual(self.contact.phone_numbers.count(), 2)
        self.assertEqual(self.contact.categories.first(), self.category)
        self.assertIsNot(self.contact.etag, None)

        data["emails"].pop(1)
        data["phone_numbers"].pop(1)
        with httmock.HTTMock(
                mocks.options_mock, mocks.put_mock, mocks.report_mock):
            response = self.client.put(url, data, format="json")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.contact.emails.count(), 1)
        self.assertEqual(self.contact.phone_numbers.count(), 1)

    def test_delete_contact(self):
        """Try to delete a contact."""
        data = {"username": self.user.username, "password": "toto"}
        response = self.client.post(reverse("core:login"), data)
        self.enable_cdav_sync()
        url = reverse("api:contact-detail", args=[self.contact.pk])
        with httmock.HTTMock(mocks.options_mock, mocks.delete_mock):
            response = self.client.delete(url)
        self.assertEqual(response.status_code, 204)
        with self.assertRaises(models.Contact.DoesNotExist):
            self.contact.refresh_from_db()


class EmailAddressViewSetTestCase(TestDataMixin, ModoAPITestCase):
    """EmailAddressViewSet tests."""

    def test_emails_list(self):
        """Check list endpoint."""
        url = reverse("api:emailaddress-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 3)

        response = self.client.get("{}?search=homer".format(url))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 1)

        response = self.client.get("{}?search=Marge".format(url))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 1)

        response = self.client.get("{}?search=Simpson".format(url))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 3)
