from AccessControl import Unauthorized
from plone import api
from plone.app.testing import login
from plone.app.testing import logout
from ploneintranet.microblog import statuscontainer
from ploneintranet.microblog.interfaces import IMicroblogContext
from ploneintranet.microblog.interfaces import IStatusContainer
from ploneintranet.microblog.statusupdate import StatusUpdate
from ploneintranet.microblog.testing import PLONEINTRANET_MICROBLOG_INTEGRATION_TESTING
from zope.interface import implementer

import Acquisition
import time
import unittest


@implementer(IStatusContainer)
class StatusContainer(statuscontainer.BaseStatusContainer):
    """Override actual implementation with unittest features"""

    def _check_add_permission(self, status):
        return

    def _blacklist_microblogcontext_uuids(self):
        return []

    def _notify_add(self, context):
        return


class UUIDStatusContainer(StatusContainer):
    def _check_delete_permission(self, status):
        return

    def _context2uuid(self, context):
        if context:
            return repr(context)


class UUIDStatusUpdate(StatusUpdate):
    def _context2uuid(self, context):
        if context:
            return repr(context)


class ContextlessStatusUpdate(StatusUpdate):
    def _init_microblog_context(self, *args):
        self._microblog_context_uuid = None

    def _init_content_context(self, *args):
        self._content_context_uuid = None


@implementer(IMicroblogContext)
class MockMicroblogContext(Acquisition.Implicit):
    """ """


class TestStatusContainer_Delete(unittest.TestCase):

    layer = PLONEINTRANET_MICROBLOG_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer["portal"]
        self.container = StatusContainer()
        self.user_admin = api.user.create(
            email="admin@example.com",
            username="user_admin",  # prevent collision with default admin
            properties={"fullname": "Site Admin"},
        )
        api.user.grant_roles(
            user=self.user_admin, roles=["Site Administrator", "Member"]
        )
        self.user_steve = api.user.create(
            email="steve@example.com",
            username="user_steve",
            properties={"fullname": "Steve User"},
        )
        api.user.grant_roles(user=self.user_steve, roles=["Member"])
        self.user_jane = api.user.create(
            email="jane@example.com",
            username="user_jane",
            properties={"fullname": "Jane User"},
        )
        api.user.grant_roles(user=self.user_jane, roles=["Member"])

    def test_admin_delete(self):
        login(self.portal, "user_steve")
        su = StatusUpdate("foo")
        self.container.add(su)
        login(self.portal, "user_admin")
        self.container.delete(su.id)
        with self.assertRaises(KeyError):
            self.container.get(su.id)

    def test_creator_delete(self):
        login(self.portal, "user_steve")
        su = StatusUpdate("foo")
        self.container.add(su)
        self.container.delete(su.id)
        with self.assertRaises(KeyError):
            self.container.get(su.id)

    def test_other_cannot_delete(self):
        login(self.portal, "user_steve")
        su = StatusUpdate("foo")
        self.container.add(su)
        login(self.portal, "user_jane")
        with self.assertRaises(Unauthorized):
            self.container.delete(su.id)

    def test_anon_cannot_delete(self):
        logout()
        su = StatusUpdate("foo")
        self.container.add(su)
        login(self.portal, "user_jane")
        with self.assertRaises(Unauthorized):
            self.container.delete(su.id)

    def test_status_mapping(self):
        su = StatusUpdate("foo")
        self.container.add(su)
        self.assertEqual(su, self.container._status_mapping.get(su.id))
        self.container.delete(su.id)
        self.assertEqual(None, self.container._status_mapping.get(su.id))

    def test_status_archive(self):
        su = StatusUpdate("foo")
        self.container.add(su)
        self.assertEqual(None, self.container._status_archive.get(su.id))
        self.container.delete(su.id)
        self.assertEqual(su, self.container._status_archive.get(su.id))

    def test_user_mapping(self):
        login(self.portal, "user_steve")
        su = StatusUpdate("foo")
        self.container.add(su)
        found = [x for x in self.container._user_mapping.get("user_steve")]
        self.assertIn(su.id, found)
        self.container.delete(su.id)
        found = [x for x in self.container._user_mapping.get("user_steve")]
        self.assertNotIn(su.id, found)

    def test_tag_mapping(self):
        su = StatusUpdate("foo", tags=["foo", "bar"])
        self.container.add(su)
        found = [x for x in self.container._tag_mapping.get("bar")]
        self.assertIn(su.id, found)
        self.container.delete(su.id)
        found = [x for x in self.container._tag_mapping.get("bar")]
        self.assertNotIn(su.id, found)

    def test_uuid_mapping(self):
        m_context = MockMicroblogContext()
        su = UUIDStatusUpdate("foo", microblog_context=m_context)
        uuid = su._microblog_context_uuid
        container = UUIDStatusContainer()
        container.add(su)
        found = [x for x in container._uuid_mapping.get(uuid)]
        self.assertIn(su.id, found)
        container.delete(su.id)
        found = [x for x in container._uuid_mapping.get(uuid)]
        self.assertNotIn(su.id, found)

    def test_content_uuid_mapping(self):
        c_context = object()
        su = UUIDStatusUpdate("foo", content_context=c_context)
        uuid = su._content_context_uuid
        container = UUIDStatusContainer()
        container.add(su)
        found = [x for x in container._content_uuid_mapping.get(uuid)]
        self.assertIn(su.id, found)
        container.delete(su.id)
        found = [x for x in container._content_uuid_mapping.get(uuid)]
        self.assertNotIn(su.id, found)

    def test_threadid_mapping(self):
        parent = ContextlessStatusUpdate("parent")
        self.container.add(parent)
        su = ContextlessStatusUpdate("child", thread_id=parent.id)
        self.container.add(su)
        found = [x for x in self.container._threadid_mapping.get(parent.id)]
        self.assertIn(su.id, found)
        self.container.delete(su.id)
        found = [x for x in self.container._threadid_mapping.get(parent.id)]
        self.assertNotIn(su.id, found)

    def test_mentions_mapping(self):
        su = StatusUpdate("foo", mention_ids=["user_steve"])
        self.container.add(su)
        found = [x for x in self.container._mentions_mapping.get("user_steve")]
        self.assertIn(su.id, found)
        self.container.delete(su.id)
        found = [x for x in self.container._mentions_mapping.get("user_steve")]
        self.assertNotIn(su.id, found)

    def test_delete_reply(self):
        parent = ContextlessStatusUpdate("parent")
        self.container.add(parent)
        su = ContextlessStatusUpdate("child", thread_id=parent.id)
        self.container.add(su)
        self.assertEqual(su, self.container.get(su.id))
        self.container.delete(su.id)
        with self.assertRaises(KeyError):
            self.container.get(su.id)
        self.assertEqual(parent, self.container.get(parent.id))

    def test_delete_thread_once(self):
        parent = ContextlessStatusUpdate("parent")
        self.container.add(parent)
        su = ContextlessStatusUpdate("child", thread_id=parent.id)
        self.container.add(su)
        self.assertEqual(su, self.container.get(su.id))
        self.container.delete(parent.id)
        with self.assertRaises(KeyError):
            self.container.get(parent.id)
        with self.assertRaises(KeyError):
            self.container.get(su.id)

    def test_delete_thread_manytimes(self):
        """Check for Heisenbug
        RuntimeError: the bucket being iterated changed size"""
        for i in range(100):
            self.test_delete_thread_once()
        # run for 3 millisecs
        until_msec = int(time.time() * 1000) + 3
        while int(time.time() * 1000) < until_msec:
            self.test_delete_thread_once()

    def test_delete_threadparent_kills_reply_other_user(self):
        login(self.portal, "user_steve")
        parent = ContextlessStatusUpdate("parent")
        self.container.add(parent)
        login(self.portal, "user_jane")
        su = ContextlessStatusUpdate("child", thread_id=parent.id)
        self.container.add(su)
        login(self.portal, "user_steve")
        self.assertEqual(su, self.container.get(su.id))
        self.container.delete(parent.id)
        with self.assertRaises(KeyError):
            self.container.get(parent.id)
        with self.assertRaises(KeyError):
            self.container.get(su.id)

    def test_is_content_mapping(self):
        container = UUIDStatusContainer()
        c_context = object()
        su1 = ContextlessStatusUpdate("parent human")
        container.add(su1)
        su2 = ContextlessStatusUpdate("child human")
        su2.thread_id = su1.id  # bypass normal __init__ lookup of parent
        container.add(su2)
        su3 = UUIDStatusUpdate("parent content", content_context=c_context)
        uuid = su3._content_context_uuid
        container.add(su3)
        su4 = UUIDStatusUpdate("child content")
        su4.thread_id = su3.id  # bypass normal __init__ lookup of parent
        su4._content_context_uuid = uuid  # bypass lookup of parent
        container.add(su4)

        found = [x for x in container._is_content_mapping]
        self.assertEqual([su3.id, su4.id], found)
        container.delete(su3.id)  # cascades to also remove reply
        found = [x for x in container._is_content_mapping]
        self.assertEqual([], found)

    def test_is_human_mapping(self):
        container = UUIDStatusContainer()
        c_context = object()
        su1 = ContextlessStatusUpdate("")
        container.add(su1)
        su2 = ContextlessStatusUpdate("")
        su2.thread_id = su1.id  # bypass normal __init__ lookup of parent
        container.add(su2)
        su3 = UUIDStatusUpdate("", content_context=c_context)
        uuid = su3._content_context_uuid
        container.add(su3)
        su4 = UUIDStatusUpdate("")
        su4.thread_id = su3.id  # bypass normal __init__ lookup of parent
        su4._content_context_uuid = uuid  # bypass lookup of parent
        container.add(su4)

        found = [int(x) for x in container._is_human_mapping]
        self.assertEqual([su1.id, su2.id, su4.id], found)
        container.delete(su1.id)  # cascades to also remove reply
        found = [x for x in container._is_human_mapping]
        self.assertEqual([su4.id], found)
