import bson
import flask
from bson import ObjectId

from pillar.tests import AbstractPillarTest


class NotificationsTest(AbstractPillarTest):
    def setUp(self, **kwargs):
        super().setUp(**kwargs)
        self.project_id, _ = self.ensure_project_exists()
        self.user1_id = self.create_user(user_id=str(bson.ObjectId()))
        self.user2_id = self.create_user(user_id=str(bson.ObjectId()))

    def test_create_node(self):
        """When a node is created, a subscription should also be created"""

        with self.app.app_context():
            self.login_api_as(self.user1_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])

            node_id = self.post_node(self.user1_id)

            self.assertSubscribed(node_id, self.user1_id)
            self.assertNotSubscribed(node_id, self.user2_id)

    def test_comment_on_own_node(self):
        """A comment on my own node should not give me any notifications"""

        with self.app.app_context():
            self.login_api_as(self.user1_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])

            node_id = self.post_node(self.user1_id)
            comment_id = self.post_comment(node_id)

            self.assertSubscribed(comment_id, self.user1_id)
            self.assertNotSubscribed(comment_id, self.user2_id)

        with self.login_as(self.user1_id):
            notification = self.notification_for_object(comment_id)
            self.assertIsNone(notification)

    def test_comment_on_node(self):
        """A comment on some one else's node should give them a notification, and subscriptions should be created"""

        with self.app.app_context():
            self.login_api_as(self.user1_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])

            node_id = self.post_node(self.user1_id)

        with self.app.app_context():
            self.login_api_as(self.user2_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])
            comment_id = self.post_comment(node_id)

            self.assertSubscribed(comment_id, self.user2_id)
            self.assertNotSubscribed(comment_id, self.user1_id)
            self.assertSubscribed(node_id, self.user1_id, self.user2_id)

        with self.login_as(self.user1_id):
            notification = self.notification_for_object(comment_id)
            self.assertIsNotNone(notification)

    def test_mark_notification_as_read(self):
        with self.app.app_context():
            self.login_api_as(self.user1_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])

            node_id = self.post_node(self.user1_id)

        with self.app.app_context():
            self.login_api_as(self.user2_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])
            comment_id = self.post_comment(node_id)

        with self.login_as(self.user1_id):
            notification = self.notification_for_object(comment_id)
            self.assertFalse(notification['is_read'])

            is_read_toggle_url = flask.url_for('notifications.action_read_toggle', notification_id=notification['_id'])
            self.get(is_read_toggle_url)

            notification = self.notification_for_object(comment_id)
            self.assertTrue(notification['is_read'])

            self.get(is_read_toggle_url)

            notification = self.notification_for_object(comment_id)
            self.assertFalse(notification['is_read'])

    def test_unsubscribe(self):
        """It should be possible to unsubscribe to notifications"""
        with self.app.app_context():
            self.login_api_as(self.user1_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])

            node_id = self.post_node(self.user1_id)

        with self.app.app_context():
            self.login_api_as(self.user2_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])
            comment_id = self.post_comment(node_id)

        with self.login_as(self.user1_id):
            notification = self.notification_for_object(comment_id)
            self.assertTrue(notification['is_subscribed'])

            is_subscribed_toggle_url =\
                flask.url_for('notifications.action_subscription_toggle', notification_id=notification['_id'])
            self.get(is_subscribed_toggle_url)

            notification = self.notification_for_object(comment_id)
            self.assertFalse(notification['is_subscribed'])

        with self.app.app_context():
            self.login_api_as(self.user2_id, roles={'subscriber', 'admin'},
                              # This group is hardcoded in the EXAMPLE_PROJECT.
                              group_ids=[ObjectId('5596e975ea893b269af85c0e')])
            comment2_id = self.post_comment(node_id)

        with self.login_as(self.user1_id):
            notification = self.notification_for_object(comment2_id)
            self.assertFalse(notification['is_subscribed'])

    def assertSubscribed(self, node_id, *user_ids):
        subscriptions_col = self.app.data.driver.db['activities-subscriptions']

        lookup = {
            'context_object': node_id,
            'notifications.web': True,
        }
        subscriptions = list(subscriptions_col.find(lookup))
        self.assertEqual(len(subscriptions), len(user_ids))
        for s in subscriptions:
            self.assertIn(s['user'], user_ids)
            self.assertEqual(s['context_object_type'], 'node')

    def assertNotSubscribed(self, node_id, user_id):
        subscriptions_col = self.app.data.driver.db['activities-subscriptions']

        lookup = {
            'context_object': node_id,
        }
        subscriptions = subscriptions_col.find(lookup)
        for s in subscriptions:
            self.assertNotEqual(s['user'], user_id)

    def notification_for_object(self, node_id):
        notifications_url = flask.url_for('notifications.index')
        notification_items = self.get(notifications_url).json['items']

        object_url = flask.url_for('nodes.redirect_to_context', node_id=str(node_id), _external=False)
        for candidate in notification_items:
            if candidate['object_url'] == object_url:
                return candidate

    def post_node(self, user_id):
        node_doc = {'description': '',
                    'project': self.project_id,
                    'node_type': 'asset',
                    'user': user_id,
                    'properties': {'status': 'published',
                                   'tags': [],
                                   'order': 0,
                                   'categories': '',
                                   },
                    'name': 'My first test node'}

        r, _, _, status = self.app.post_internal('nodes', node_doc)
        self.assertEqual(status, 201, r)
        node_id = r['_id']
        return node_id

    def post_comment(self, node_id):
        comment_url = flask.url_for('nodes_api.post_node_comment', node_path=str(node_id))
        comment = self.post(
            comment_url,
            json={
                'msg': 'je möder lives at [home](https://cloud.blender.org/)',
            },
            expected_status=201,
        )
        return ObjectId(comment.json['id'])
