from datetime import datetime
from functools import partial
from ploneintranet.messaging.testing import IntegrationTestCase
from zope.interface.verify import verifyClass

import pytz

now = partial(datetime.now, pytz.utc)


class ApiTestCase(IntegrationTestCase):
    def setUp(self):
        from ploneintranet.messaging.messaging import Inboxes

        self.inboxes = Inboxes()

    def _create_inbox(self, username="testuser"):
        return self.inboxes.add_inbox(username)

    def _create_conversation(
        self, inbox_user="testuser", other_user="otheruser", created=None
    ):
        from ploneintranet.messaging.messaging import Conversation

        if created is None:
            created = now()
        inbox = self._create_inbox(username=inbox_user)
        return inbox.add_conversation(Conversation(other_user, created))


class TestInboxes(ApiTestCase):
    def test_inboxes_interface(self):
        from ploneintranet.messaging.interfaces import IInboxes
        from ploneintranet.messaging.messaging import Inboxes

        verifyClass(IInboxes, Inboxes)

    def test_inboxes_add_inbox(self):
        from ploneintranet.messaging.messaging import Inbox

        self.assertEqual(list(self.inboxes.keys()), [])
        new_inbox = self.inboxes.add_inbox("testuser")
        self.assertEqual(list(self.inboxes.keys()), ["testuser"])
        self.assertIs(type(self.inboxes["testuser"]), Inbox)
        self.assertIs(new_inbox, self.inboxes["testuser"])

    def test_inboxes_add_inbox_assigns_parent(self):
        inbox = self.inboxes.add_inbox("testuser")
        self.assertIs(inbox.__parent__, self.inboxes)

    def test_inboxes_dictapi_add_inbox(self):
        from ploneintranet.messaging.messaging import Inbox

        inbox = Inbox("testuser")
        self.inboxes["testuser"] = inbox
        self.assertIs(self.inboxes["testuser"], inbox)

    def test_inboxes_dictapi_add_inbox_assigns_parent(self):
        from ploneintranet.messaging.messaging import Inbox

        inbox = Inbox("testuser")
        self.inboxes["testuser"] = inbox
        self.assertIs(inbox.__parent__, self.inboxes)

    def test_inboxes_dictapi_add_inbox_checks_key(self):
        from ploneintranet.messaging.messaging import Inbox

        inbox = Inbox("testuser")
        with self.assertRaises(KeyError):
            self.inboxes["brokenkey"] = inbox

    def test_inboxes_dictapi_add_inbox_checks_interface(self):
        from zope.interface.exceptions import MultipleInvalid

        class FakeInbox:
            username = "testuser"

        inbox = FakeInbox()
        with self.assertRaises(MultipleInvalid):
            self.inboxes["testuser"] = inbox

    def test_inboxes_dictapi_get_inbox(self):
        self.assertEqual(list(self.inboxes.keys()), [])
        inbox = self._create_inbox("testuser")
        self.assertIs(inbox, self.inboxes["testuser"])

    def test_inboxes_dictapi_get_missing_inbox(self):
        self.assertEqual(list(self.inboxes.keys()), [])
        with self.assertRaises(KeyError):
            self.inboxes["nonexisting"]

    def test_inboxes_send_message_does_it_all(self):
        self.assertEqual(list(self.inboxes.keys()), [])
        _now = now()
        self.inboxes.send_message("sendername", "recipientname", "text", _now)
        sender_message = self.inboxes["sendername"]["recipientname"].get_messages()[0]
        recipient_message = self.inboxes["recipientname"]["sendername"].get_messages()[
            0
        ]

        # messages are equal...
        for attr in ("sender", "recipient", "text", "created"):
            self.assertEqual(
                getattr(sender_message, attr, object()),
                getattr(recipient_message, attr, object()),
            )
        # ...but not identical
        self.assertIsNot(sender_message, recipient_message)


class TestInbox(ApiTestCase):
    def test_inbox_interface(self):
        from ploneintranet.messaging.interfaces import IInbox
        from ploneintranet.messaging.messaging import Inbox

        verifyClass(IInbox, Inbox)

    def test_inbox_provides_iinbox(self):
        from ploneintranet.messaging.interfaces import IInbox

        new_inbox = self.inboxes.add_inbox("testuser")
        self.assertTrue(IInbox.providedBy(new_inbox))

    def test_inbox_get_empty_conversations(self):
        inbox = self._create_inbox()
        self.assertEqual(list(inbox.get_conversations()), [])

    def test_inbox_get_conversations(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        inbox.add_conversation(Conversation("otheruser", now()))
        inbox.add_conversation(Conversation("thirduser", now()))
        conversations = inbox.get_conversations()
        self.assertEqual(len(conversations), 2)
        self.assertEqual(type(conversations[0]), Conversation)
        self.assertEqual(type(conversations[1]), Conversation)
        self.assertNotEqual(conversations[0], conversations[1])

    def test_inbox_add_conversation(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        conversation = Conversation("otheruser", now())
        inbox.add_conversation(conversation)
        self.assertIs(conversation, inbox[conversation.username])

    def test_inbox_add_conversation_adds_parent(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        conversation = Conversation("otheruser", now())
        inbox.add_conversation(conversation)
        self.assertIs(conversation.__parent__, inbox)

    def test_inbox_dictapi_add_conversation(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        conversation = Conversation("otheruser", now())
        inbox[conversation.username] = conversation
        self.assertIs(conversation, inbox[conversation.username])

    def test_inbox_dictapi_add_conversation_adds_parent(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        conversation = Conversation("otheruser", now())
        inbox[conversation.username] = conversation
        self.assertIs(conversation.__parent__, inbox)

    def test_inbox_dictapi_add_conversation_checks_key(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        conversation = Conversation("otheruser", now())
        with self.assertRaises(KeyError):
            inbox["brokenkey"] = conversation

    def test_inbox_dictapi_add_conversation_checks_conversation_impl(self):
        from zope.interface.exceptions import MultipleInvalid

        class FakeConversation:
            username = "otheruser"

        inbox = self._create_inbox("testuser")
        conversation = FakeConversation()

        with self.assertRaises(MultipleInvalid):
            inbox["otheruser"] = conversation

    def test_inbox_dictapi_add_conversation_checks_users(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox("testuser")
        conversation = Conversation("testuser", now())
        with self.assertRaises(ValueError) as cm:
            inbox["testuser"] = conversation

        self.assertEqual(str(cm.exception), "You can't speak to yourself")

    def test_inbox_delete_conversation(self):
        from ploneintranet.messaging.messaging import Conversation

        inbox = self._create_inbox()
        inbox.add_conversation(Conversation("otheruser", now()))
        conversations = inbox.get_conversations()
        self.assertEqual(len(conversations), 1)

        del inbox["otheruser"]
        conversations = inbox.get_conversations()
        self.assertEqual(len(conversations), 0)

    def test_inbox_user_initially_isnt_blocked(self):
        inbox = self._create_inbox()
        self.assertEqual(inbox.is_blocked("dontlike"), False)

    def test_inbox_initial_new_messages_count(self):
        inbox = self._create_inbox()
        self.assertEqual(inbox.new_messages_count, 0)


class TestConversation(ApiTestCase):
    def test_conversation_interface(self):
        from ploneintranet.messaging.interfaces import IConversation
        from ploneintranet.messaging.messaging import Conversation

        verifyClass(IConversation, Conversation)

    def test_conversation_initially_has_no_message(self):
        conversation = self._create_conversation()
        self.assertEqual(list(conversation.get_messages()), [])

    def test_conversation_initial_message_count_is_zero(self):
        conversation = self._create_conversation()
        self.assertEqual(conversation.new_messages_count, 0)

    def test_conversation_initially_has_no_last(self):
        conversation = self._create_conversation()
        self.assertEqual(conversation.last, None)

    def test_conversation_add_message(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        message = Message("inbox_user", "other_user", "test", now())
        conversation.add_message(message)
        messages = conversation.get_messages()
        self.assertEqual(list(messages), [message])

    def test_conversation_add_message_increases_unread_count(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        self.assertEqual(conversation.new_messages_count, 0)

        message = Message("inbox_user", "other_user", "test", now())
        conversation.add_message(message)
        self.assertEqual(list(conversation.get_messages())[0].new, True)
        self.assertEqual(conversation.new_messages_count, 1)

    def test_conversation_send_message_set_last(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        message = Message("inbox_user", "other_user", "test", now())
        conversation.add_message(message)
        self.assertEqual(conversation.last, message)

    def test_conversation_receive_message_set_last(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        message = Message("other_user", "inbox_user", "test", now())
        conversation.add_message(message)
        self.assertEqual(conversation.last, message)

    def test_conversation_dictapi_delete_message(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")

        message = Message("inbox_user", "other_user", "test", now())
        uid = conversation.add_message(message)
        self.assertEqual(list(conversation.get_messages()), [message])

        del conversation[uid]
        self.assertEqual(list(conversation.get_messages()), [])

    def test_conversation_delete_message_decreases_unread_count(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        message = Message("inbox_user", "other_user", "test", now())
        uid = conversation.add_message(message)
        self.assertEqual(conversation.new_messages_count, 1)

        del conversation[uid]
        self.assertEqual(conversation.new_messages_count, 0)

    def test_conversation_add_message_increases_inbox_unread_count(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        inbox = conversation.__parent__
        self.assertEqual(inbox.new_messages_count, 0)

        message = Message("inbox_user", "other_user", "test", now())
        conversation.add_message(message)
        self.assertEqual(inbox.new_messages_count, 1)

    def test_conversation_delete_message_decreases_inbox_unread_count(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        inbox = conversation.__parent__
        message = Message("inbox_user", "other_user", "test", now())
        uid = conversation.add_message(message)
        self.assertEqual(inbox.new_messages_count, 1)

        del conversation[uid]
        self.assertEqual(inbox.new_messages_count, 0)

    def test_conversation_mark_read(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        message = Message("inbox_user", "other_user", "test", now())
        conversation.add_message(message)
        self.assertEqual(message.new, True)

        conversation.mark_read()
        self.assertEqual(conversation.new_messages_count, 0)
        self.assertEqual(message.new, False)

    def test_conversation_mark_read_updates_inbox_new_messages_count(self):
        from ploneintranet.messaging.messaging import Message

        conversation = self._create_conversation("inbox_user", "other_user")
        inbox = conversation.__parent__
        message = Message("inbox_user", "other_user", "test", now())

        conversation.add_message(message)
        self.assertEqual(inbox.new_messages_count, 1)

        conversation.mark_read()
        self.assertEqual(inbox.new_messages_count, 0)


class TestMessage(ApiTestCase):
    def test_message_interface(self):
        from ploneintranet.messaging.interfaces import IMessage
        from ploneintranet.messaging.messaging import Message

        verifyClass(IMessage, Message)

    def test_message_attributes(self):
        from ploneintranet.messaging.messaging import Message

        _now = now()
        message = Message("sender", "recipient", "text", _now)
        self.assertEqual(message.sender, "sender")
        self.assertEqual(message.recipient, "recipient")
        self.assertEqual(message.text, "text")
        self.assertEqual(message.created, _now)

    def test_message_asserts_created(self):
        from ploneintranet.messaging.messaging import Message

        not_a_date = "not a date"
        with self.assertRaises(ValueError):
            Message("sender", "recipient", "text", not_a_date)
