# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This file contains tests of the "presence-like" data that is shared between
presence and profiles; namely, the displayname and avatar_url."""

from tests import unittest
from twisted.internet import defer

from mock import Mock, call, ANY, NonCallableMock

from ..utils import MockClock, setup_test_homeserver

from synapse.api.constants import PresenceState
from synapse.handlers.presence import PresenceHandler
from synapse.handlers.profile import ProfileHandler
from synapse.types import UserID


OFFLINE = PresenceState.OFFLINE
UNAVAILABLE = PresenceState.UNAVAILABLE
ONLINE = PresenceState.ONLINE


class MockReplication(object):
    def __init__(self):
        self.edu_handlers = {}

    def register_edu_handler(self, edu_type, handler):
        self.edu_handlers[edu_type] = handler

    def register_query_handler(self, query_type, handler):
        pass

    def received_edu(self, origin, edu_type, content):
        self.edu_handlers[edu_type](origin, content)


class PresenceAndProfileHandlers(object):
    def __init__(self, hs):
        self.presence_handler = PresenceHandler(hs)
        self.profile_handler = ProfileHandler(hs)


class PresenceProfilelikeDataTestCase(unittest.TestCase):

    @defer.inlineCallbacks
    def setUp(self):
        hs = yield setup_test_homeserver(
            clock=MockClock(),
            datastore=Mock(spec=[
                "set_presence_state",
                "is_presence_visible",
                "set_profile_displayname",
                "get_rooms_for_user",
            ]),
            handlers=None,
            resource_for_federation=Mock(),
            http_client=None,
            replication_layer=MockReplication(),
            ratelimiter=NonCallableMock(spec_set=[
                "send_message",
            ]),
        )
        self.ratelimiter = hs.get_ratelimiter()
        self.ratelimiter.send_message.return_value = (True, 0)
        hs.handlers = PresenceAndProfileHandlers(hs)

        self.datastore = hs.get_datastore()

        self.replication = hs.get_replication_layer()
        self.replication.send_edu = Mock()

        def send_edu(*args, **kwargs):
            # print "send_edu: %s, %s" % (args, kwargs)
            return defer.succeed((200, "OK"))
        self.replication.send_edu.side_effect = send_edu

        def get_profile_displayname(user_localpart):
            return defer.succeed("Frank")
        self.datastore.get_profile_displayname = get_profile_displayname

        def is_presence_visible(*args, **kwargs):
            return defer.succeed(False)
        self.datastore.is_presence_visible = is_presence_visible

        def get_profile_avatar_url(user_localpart):
            return defer.succeed("http://foo")
        self.datastore.get_profile_avatar_url = get_profile_avatar_url

        self.presence_list = [
            {"observed_user_id": "@banana:test", "accepted": True},
            {"observed_user_id": "@clementine:test", "accepted": True},
        ]
        def get_presence_list(user_localpart, accepted=None):
            return defer.succeed(self.presence_list)
        self.datastore.get_presence_list = get_presence_list

        def user_rooms_intersect(userlist):
            return defer.succeed(False)
        self.datastore.user_rooms_intersect = user_rooms_intersect

        self.handlers = hs.get_handlers()

        self.mock_update_client = Mock()
        def update(*args, **kwargs):
            # print "mock_update_client: %s, %s" %(args, kwargs)
            return defer.succeed(None)
        self.mock_update_client.side_effect = update

        self.handlers.presence_handler.push_update_to_clients = (
                self.mock_update_client)

        hs.handlers.room_member_handler = Mock(spec=[
            "get_joined_rooms_for_user",
        ])
        hs.handlers.room_member_handler.get_joined_rooms_for_user = (
                lambda u: defer.succeed([]))

        # Some local users to test with
        self.u_apple = UserID.from_string("@apple:test")
        self.u_banana = UserID.from_string("@banana:test")
        self.u_clementine = UserID.from_string("@clementine:test")

        # Remote user
        self.u_potato = UserID.from_string("@potato:remote")

        self.mock_get_joined = (
            self.datastore.get_rooms_for_user
        )

    @defer.inlineCallbacks
    def test_set_my_state(self):
        self.presence_list = [
            {"observed_user_id": "@banana:test", "accepted": True},
            {"observed_user_id": "@clementine:test", "accepted": True},
        ]

        mocked_set = self.datastore.set_presence_state
        mocked_set.return_value = defer.succeed({"state": OFFLINE})

        yield self.handlers.presence_handler.set_state(
                target_user=self.u_apple, auth_user=self.u_apple,
                state={"presence": UNAVAILABLE, "status_msg": "Away"})

        mocked_set.assert_called_with("apple",
            {"state": UNAVAILABLE, "status_msg": "Away"}
        )

    @defer.inlineCallbacks
    def test_push_local(self):
        def get_joined(*args):
            return defer.succeed([])

        self.mock_get_joined.side_effect = get_joined

        self.presence_list = [
            {"observed_user_id": "@banana:test", "accepted": True},
            {"observed_user_id": "@clementine:test", "accepted": True},
        ]

        self.datastore.set_presence_state.return_value = defer.succeed(
            {"state": ONLINE}
        )

        # TODO(paul): Gut-wrenching
        from synapse.handlers.presence import UserPresenceCache
        self.handlers.presence_handler._user_cachemap[self.u_apple] = (
            UserPresenceCache()
        )
        self.handlers.presence_handler._user_cachemap[self.u_apple].update(
            {"presence": OFFLINE}, serial=0
        )
        apple_set = self.handlers.presence_handler._local_pushmap.setdefault(
                "apple", set())
        apple_set.add(self.u_banana)
        apple_set.add(self.u_clementine)

        yield self.handlers.presence_handler.set_state(self.u_apple,
            self.u_apple, {"presence": ONLINE}
        )
        yield self.handlers.presence_handler.set_state(self.u_banana,
            self.u_banana, {"presence": ONLINE}
        )

        presence = yield self.handlers.presence_handler.get_presence_list(
                observer_user=self.u_apple, accepted=True)

        self.assertEquals([
            {"observed_user": self.u_banana,
                "presence": ONLINE,
                "last_active_ago": 0,
                "displayname": "Frank",
                "avatar_url": "http://foo",
                "accepted": True},
            {"observed_user": self.u_clementine,
                "presence": OFFLINE,
                "accepted": True}
        ], presence)

        self.mock_update_client.assert_has_calls([
            call(
                users_to_push={self.u_apple, self.u_banana, self.u_clementine},
                room_ids=[]
            ),
        ], any_order=True)

        self.mock_update_client.reset_mock()

        self.datastore.set_profile_displayname.return_value = defer.succeed(
                None)

        yield self.handlers.profile_handler.set_displayname(self.u_apple,
                self.u_apple, "I am an Apple")

        self.mock_update_client.assert_has_calls([
            call(
                users_to_push={self.u_apple, self.u_banana, self.u_clementine},
                room_ids=[],
            ),
        ], any_order=True)

    @defer.inlineCallbacks
    def test_push_remote(self):
        self.presence_list = [
            {"observed_user_id": "@potato:remote", "accepted": True},
        ]

        self.datastore.set_presence_state.return_value = defer.succeed(
            {"state": ONLINE}
        )

        # TODO(paul): Gut-wrenching
        from synapse.handlers.presence import UserPresenceCache
        self.handlers.presence_handler._user_cachemap[self.u_apple] = (
            UserPresenceCache()
        )
        self.handlers.presence_handler._user_cachemap[self.u_apple].update(
            {"presence": OFFLINE}, serial=0
        )
        apple_set = self.handlers.presence_handler._remote_sendmap.setdefault(
                "apple", set())
        apple_set.add(self.u_potato.domain)

        yield self.handlers.presence_handler.set_state(self.u_apple,
            self.u_apple, {"presence": ONLINE}
        )

        self.replication.send_edu.assert_called_with(
                destination="remote",
                edu_type="m.presence",
                content={
                    "push": [
                        {"user_id": "@apple:test",
                         "presence": "online",
                         "last_active_ago": 0,
                         "displayname": "Frank",
                         "avatar_url": "http://foo"},
                    ],
                },
        )

    @defer.inlineCallbacks
    def test_recv_remote(self):
        self.presence_list = [
            {"observed_user_id": "@banana:test"},
            {"observed_user_id": "@clementine:test"},
        ]

        # TODO(paul): Gut-wrenching
        potato_set = self.handlers.presence_handler._remote_recvmap.setdefault(
            self.u_potato, set()
        )
        potato_set.add(self.u_apple)

        yield self.replication.received_edu(
            "remote", "m.presence", {
                "push": [
                    {"user_id": "@potato:remote",
                     "presence": "online",
                     "displayname": "Frank",
                     "avatar_url": "http://foo"},
                ],
            }
        )

        self.mock_update_client.assert_called_with(
            users_to_push=set([self.u_apple]),
            room_ids=[],
        )

        state = yield self.handlers.presence_handler.get_state(self.u_potato,
                self.u_apple)

        self.assertEquals(
                {"presence": ONLINE,
                 "displayname": "Frank",
                 "avatar_url": "http://foo"},
            state)