import pytest
import responses
import json
from copy import deepcopy
from matrix_client.client import MatrixClient, Room, User, CACHE
from matrix_client.api import MATRIX_V2_API_PATH
from . import response_examples
try:
    from urllib import quote
except ImportError:
    from urllib.parse import quote

HOSTNAME = "http://example.com"


def test_create_client():
    MatrixClient("http://example.com")


@responses.activate
def test_create_client_with_token():
    user_id = "@alice:example.com"
    token = "Dp0YKRXwx0iWDhFj7lg3DVjwsWzGcUIgARljgyAip2JD8qd5dSaW" \
            "cxowTKEFetPulfLijAhv8eOmUSScyGcWgZyNMRTBmoJ0RFc0HotPvTBZ" \
            "U98yKRLtat7V43aCpFmK"
    whoami_url = HOSTNAME+MATRIX_V2_API_PATH+"/account/whoami"
    responses.add(
        responses.GET,
        whoami_url,
        body='{"user_id": "%s"}' % user_id
    )
    sync_response = deepcopy(response_examples.example_sync)
    response_body = json.dumps(sync_response)
    sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync"
    responses.add(responses.GET, sync_url, body=response_body)
    MatrixClient(HOSTNAME, token=token)
    req = responses.calls[0].request
    assert req.method == 'GET'
    assert whoami_url in req.url


def test_sync_token():
    client = MatrixClient("http://example.com")
    assert client.get_sync_token() is None
    client.set_sync_token("FAKE_TOKEN")
    assert client.get_sync_token() == "FAKE_TOKEN"


def test__mkroom():
    client = MatrixClient("http://example.com")

    roomId = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    goodRoom = client._mkroom(roomId)

    assert isinstance(goodRoom, Room)
    assert goodRoom.room_id is roomId

    with pytest.raises(ValueError):
        client._mkroom("BAD_ROOM:matrix.org")
        client._mkroom("!BAD_ROOMmatrix.org")
        client._mkroom("!BAD_ROOM::matrix.org")


def test_get_rooms():
    client = MatrixClient("http://example.com")
    rooms = client.get_rooms()
    assert isinstance(rooms, dict)
    assert len(rooms) == 0

    client = MatrixClient("http://example.com")

    client._mkroom("!abc:matrix.org")
    client._mkroom("!def:matrix.org")
    client._mkroom("!ghi:matrix.org")

    rooms = client.get_rooms()
    assert isinstance(rooms, dict)
    assert len(rooms) == 3


def test_bad_state_events():
    client = MatrixClient("http://example.com")
    room = client._mkroom("!abc:matrix.org")

    ev = {
        "tomato": False
    }

    room._process_state_event(ev)


def test_state_event():
    client = MatrixClient("http://example.com")
    room = client._mkroom("!abc:matrix.org")

    room.name = False
    room.topic = False
    room.aliases = False

    ev = {
        "type": "m.room.name",
        "content": {},
        "event_id": "$10000000000000AAAAA:matrix.org"
    }

    room._process_state_event(ev)
    assert room.name is None

    ev["content"]["name"] = "TestName"
    room._process_state_event(ev)
    assert room.name == "TestName"

    ev["type"] = "m.room.topic"
    room._process_state_event(ev)
    assert room.topic is None

    ev["content"]["topic"] = "TestTopic"
    room._process_state_event(ev)
    assert room.topic == "TestTopic"

    ev["type"] = "m.room.aliases"
    room._process_state_event(ev)
    assert room.aliases is None

    aliases = ["#foo:matrix.org", "#bar:matrix.org"]
    ev["content"]["aliases"] = aliases
    room._process_state_event(ev)
    assert room.aliases is aliases

    # test member join event
    ev["type"] = "m.room.member"
    ev["content"] = {'membership': 'join', 'displayname': 'stereo'}
    ev["state_key"] = "@stereo:xxx.org"
    room._process_state_event(ev)
    assert len(room._members) == 1
    assert room._members["@stereo:xxx.org"]
    # test member leave event
    ev["content"]['membership'] = 'leave'
    room._process_state_event(ev)
    assert len(room._members) == 0

    # test join_rules
    room.invite_only = False
    ev["type"] = "m.room.join_rules"
    ev["content"] = {"join_rule": "invite"}
    room._process_state_event(ev)
    assert room.invite_only

    # test guest_access
    room.guest_access = False
    ev["type"] = "m.room.guest_access"
    ev["content"] = {"guest_access": "can_join"}
    room._process_state_event(ev)
    assert room.guest_access

    # test malformed event (check does not throw exception)
    room.guest_access = False
    ev["type"] = "m.room.guest_access"
    ev["content"] = {}
    room._process_state_event(ev)
    assert not room.guest_access

    # test encryption
    room.encrypted = False
    ev["type"] = "m.room.encryption"
    ev["content"] = {"algorithm": "m.megolm.v1.aes-sha2"}
    room._process_state_event(ev)
    assert room.encrypted
    # encrypted flag must not be cleared on configuration change
    ev["content"] = {"algorithm": None}
    room._process_state_event(ev)
    assert room.encrypted


def test_get_user():
    client = MatrixClient("http://example.com")

    assert isinstance(client.get_user("@foobar:matrix.org"), User)

    with pytest.raises(ValueError):
        client.get_user("badfoobar:matrix.org")
        client.get_user("@badfoobarmatrix.org")
        client.get_user("@badfoobar:::matrix.org")


def test_get_download_url():
    client = MatrixClient("http://example.com")
    real_url = "http://example.com/_matrix/media/r0/download/foobar"
    assert client.api.get_download_url("mxc://foobar") == real_url

    with pytest.raises(ValueError):
        client.api.get_download_url("http://foobar")


def test_remove_listener():
    def dummy_listener():
        pass

    client = MatrixClient("http://example.com")
    handler = client.add_listener(dummy_listener)

    found_listener = False
    for listener in client.listeners:
        if listener["uid"] == handler:
            found_listener = True
            break

    assert found_listener, "listener was not added properly"

    client.remove_listener(handler)
    found_listener = False
    for listener in client.listeners:
        if listener["uid"] == handler:
            found_listener = True
            break

    assert not found_listener, "listener was not removed properly"


class TestClientRegister:
    cli = MatrixClient(HOSTNAME)

    @responses.activate
    def test_register_as_guest(self):
        cli = self.cli

        def _sync(self):
            self._sync_called = True
        cli.__dict__[_sync.__name__] = _sync.__get__(cli, cli.__class__)
        register_guest_url = HOSTNAME + MATRIX_V2_API_PATH + "/register"
        response_body = json.dumps({
            'access_token': 'EXAMPLE_ACCESS_TOKEN',
            'device_id': 'guest_device',
            'home_server': 'example.com',
            'user_id': '@455:example.com'
        })
        responses.add(responses.POST, register_guest_url, body=response_body)
        cli.register_as_guest()
        assert cli.token == cli.api.token == 'EXAMPLE_ACCESS_TOKEN'
        assert cli.hs == 'example.com'
        assert cli.user_id == '@455:example.com'
        assert cli._sync_called


def test_get_rooms_display_name():

    def add_members(api, room, num):
        for i in range(num):
            room._add_member('@frho%s:matrix.org' % i, 'ho%s' % i)

    client = MatrixClient("http://example.com")
    client.user_id = "@frho0:matrix.org"
    room1 = client._mkroom("!abc:matrix.org")
    add_members(client.api, room1, 1)
    room2 = client._mkroom("!def:matrix.org")
    add_members(client.api, room2, 2)
    room3 = client._mkroom("!ghi:matrix.org")
    add_members(client.api, room3, 3)
    room4 = client._mkroom("!rfi:matrix.org")
    add_members(client.api, room4, 30)

    rooms = client.get_rooms()
    assert len(rooms) == 4
    assert room1.display_name == "Empty room"
    assert room2.display_name == "ho1"
    assert room3.display_name == "ho1 and ho2"
    assert room4.display_name == "ho1 and 28 others"


@responses.activate
def test_presence_listener():
    client = MatrixClient("http://example.com")
    accumulator = []

    def dummy_callback(event):
        accumulator.append(event)
    presence_events = [
        {
            "content": {
                "avatar_url": "mxc://localhost:wefuiwegh8742w",
                "currently_active": False,
                "last_active_ago": 2478593,
                "presence": "online",
                "user_id": "@example:localhost"
            },
            "event_id": "$WLGTSEFSEF:localhost",
            "type": "m.presence"
        },
        {
            "content": {
                "avatar_url": "mxc://localhost:weaugwe742w",
                "currently_active": True,
                "last_active_ago": 1478593,
                "presence": "online",
                "user_id": "@example2:localhost"
            },
            "event_id": "$CIGTXEFREF:localhost",
            "type": "m.presence"
        },
        {
            "content": {
                "avatar_url": "mxc://localhost:wefudweg13742w",
                "currently_active": False,
                "last_active_ago": 24795,
                "presence": "offline",
                "user_id": "@example3:localhost"
            },
            "event_id": "$ZEGASEDSEF:localhost",
            "type": "m.presence"
        },
    ]
    sync_response = deepcopy(response_examples.example_sync)
    sync_response["presence"]["events"] = presence_events
    response_body = json.dumps(sync_response)
    sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync"

    responses.add(responses.GET, sync_url, body=response_body)
    callback_uid = client.add_presence_listener(dummy_callback)
    client._sync()
    assert accumulator == presence_events

    responses.add(responses.GET, sync_url, body=response_body)
    client.remove_presence_listener(callback_uid)
    accumulator = []
    client._sync()
    assert accumulator == []


@responses.activate
def test_changing_user_power_levels():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.power_levels"

    # Code should first get current power_levels and then modify them
    responses.add(responses.GET, PL_state_path,
                  json=response_examples.example_pl_event["content"])
    responses.add(responses.PUT, PL_state_path,
                  json=response_examples.example_event_response)
    # Removes user from user and adds user to to users list
    assert room.modify_user_power_levels(users={"@example:localhost": None,
                                                "@foobar:example.com": 49})

    expected_request = deepcopy(response_examples.example_pl_event["content"])
    del expected_request["users"]["@example:localhost"]
    expected_request["users"]["@foobar:example.com"] = 49

    assert json.loads(responses.calls[1].request.body) == expected_request


@responses.activate
def test_changing_default_power_level():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.power_levels"

    # Code should first get current power_levels and then modify them
    responses.add(responses.GET, PL_state_path,
                  json=response_examples.example_pl_event["content"])
    responses.add(responses.PUT, PL_state_path,
                  json=response_examples.example_event_response)
    assert room.modify_user_power_levels(users_default=23)

    expected_request = deepcopy(response_examples.example_pl_event["content"])
    expected_request["users_default"] = 23

    assert json.loads(responses.calls[1].request.body) == expected_request


@responses.activate
def test_changing_event_required_power_levels():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.power_levels"

    # Code should first get current power_levels and then modify them
    responses.add(responses.GET, PL_state_path,
                  json=response_examples.example_pl_event["content"])
    responses.add(responses.PUT, PL_state_path,
                  json=response_examples.example_event_response)
    # Remove event from events and adds new controlled event
    assert room.modify_required_power_levels(events={"m.room.name": None,
                                                     "example.event": 51})

    expected_request = deepcopy(response_examples.example_pl_event["content"])
    del expected_request["events"]["m.room.name"]
    expected_request["events"]["example.event"] = 51

    assert json.loads(responses.calls[1].request.body) == expected_request


@responses.activate
def test_changing_other_required_power_levels():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.power_levels"

    # Code should first get current power_levels and then modify them
    responses.add(responses.GET, PL_state_path,
                  json=response_examples.example_pl_event["content"])
    responses.add(responses.PUT, PL_state_path,
                  json=response_examples.example_event_response)
    # Remove event from events and adds new controlled event
    assert room.modify_required_power_levels(kick=53, redact=2,
                                             state_default=None)

    expected_request = deepcopy(response_examples.example_pl_event["content"])
    expected_request["kick"] = 53
    expected_request["redact"] = 2
    del expected_request["state_default"]

    assert json.loads(responses.calls[1].request.body) == expected_request


@responses.activate
def test_cache():
    m_none = MatrixClient("http://example.com", cache_level=CACHE.NONE)
    m_some = MatrixClient("http://example.com", cache_level=CACHE.SOME)
    m_all = MatrixClient("http://example.com", cache_level=CACHE.ALL)
    sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync"
    room_id = "!726s6s6q:example.com"
    room_name = "The FooBar"
    sync_response = deepcopy(response_examples.example_sync)

    with pytest.raises(ValueError):
        MatrixClient("http://example.com", cache_level=1)
        MatrixClient("http://example.com", cache_level=5)
        MatrixClient("http://example.com", cache_level=0.5)
        MatrixClient("http://example.com", cache_level=-5)
        MatrixClient("http://example.com", cache_level="foo")
        MatrixClient("http://example.com", cache_level=0.0)

    sync_response["rooms"]["join"][room_id]["state"]["events"].append(
        {
            "sender": "@alice:example.com",
            "type": "m.room.name",
            "state_key": "",
            "content": {"name": room_name},
        }
    )

    responses.add(responses.GET, sync_url, json.dumps(sync_response))
    m_none._sync()
    responses.add(responses.GET, sync_url, json.dumps(sync_response))
    m_some._sync()
    responses.add(responses.GET, sync_url, json.dumps(sync_response))
    m_all._sync()

    assert m_none.rooms[room_id].name is None
    assert m_some.rooms[room_id].name == room_name
    assert m_all.rooms[room_id].name == room_name

    assert m_none.rooms[room_id]._members == m_some.rooms[room_id]._members == {}
    assert len(m_all.rooms[room_id]._members) == 2
    assert m_all.rooms[room_id]._members["@alice:example.com"]


@responses.activate
def test_room_join_rules():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    assert room.invite_only is None
    join_rules_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.join_rules"

    responses.add(responses.PUT, join_rules_state_path,
                  json=response_examples.example_event_response)

    assert room.set_invite_only(True)
    assert room.invite_only


@responses.activate
def test_room_guest_access():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    assert room.guest_access is None
    guest_access_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.guest_access"

    responses.add(responses.PUT, guest_access_state_path,
                  json=response_examples.example_event_response)

    assert room.set_guest_access(True)
    assert room.guest_access


@responses.activate
def test_enable_encryption():
    pytest.importorskip('olm')
    client = MatrixClient(HOSTNAME, encryption=True)

    login_path = HOSTNAME + MATRIX_V2_API_PATH + "/login"
    responses.add(responses.POST, login_path,
                  json=response_examples.example_success_login_response)

    upload_path = HOSTNAME + MATRIX_V2_API_PATH + '/keys/upload'
    responses.add(responses.POST, upload_path, body='{"one_time_key_counts": {}}')

    client.login("@example:localhost", "password", sync=False)

    assert client.olm_device


@responses.activate
def test_enable_encryption_in_room():
    client = MatrixClient(HOSTNAME)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
    room = client._mkroom(room_id)
    assert not room.encrypted
    encryption_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.encryption"

    responses.add(responses.PUT, encryption_state_path,
                  json=response_examples.example_event_response)

    assert room.enable_encryption()
    assert room.encrypted


@responses.activate
def test_detect_encryption_state():
    client = MatrixClient(HOSTNAME, encryption=True)
    room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"

    encryption_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
        "/rooms/" + quote(room_id) + "/state/m.room.encryption"
    responses.add(responses.GET, encryption_state_path,
                  json={"algorithm": "m.megolm.v1.aes-sha2"})
    responses.add(responses.GET, encryption_state_path,
                  json={}, status=404)

    room = client._mkroom(room_id)
    assert room.encrypted

    room = client._mkroom(room_id)
    assert not room.encrypted


@responses.activate
def test_one_time_keys_sync():
    client = MatrixClient(HOSTNAME, encryption=True)
    sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync"
    sync_response = deepcopy(response_examples.example_sync)
    payload = {'dummy': 1}
    sync_response["device_one_time_keys_count"] = payload
    sync_response['rooms']['join'] = {}

    class DummyDevice:

        def update_one_time_key_counts(self, payload):
            self.payload = payload

    device = DummyDevice()
    client.olm_device = device

    responses.add(responses.GET, sync_url, json=sync_response)

    client._sync()
    assert device.payload == payload
