from django.test import Client

from authentication.tests import SignatureAuthClient, UserTestMixin, GroupTestMixin, ToolshedTestCase, \
    DummyExternalUser
from authentication.models import Group, GroupInvite, GroupInviteIncoming, KnownIdentity

client = SignatureAuthClient()


class GroupModelTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
    def setUp(self):
        super().setUp()
        self.prepare_users()
        self.prepare_groups()

    def test_group_str(self):
        self.assertEqual(str(self.f['group1']), '#group1@' + self.f['example_com'].name)

    def test_is_member(self):
        self.assertTrue(self.f['group1'].is_member(self.f['local_user1'].public_identity))
        self.assertFalse(self.f['group1'].is_member(self.f['local_user2'].public_identity))


class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
    def setUp(self):
        super().setUp()
        self.prepare_users()

    def test_create_group(self):
        reply = client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'})
        self.assertEqual(reply.status_code, 201)
        group = Group.objects.get(name='workshop')
        self.assertEqual(group.domain, self.f['local_user1'].domain)
        self.assertTrue(group.is_member(self.f['local_user1'].public_identity))

    def test_create_group_duplicate_name(self):
        client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'})
        reply = client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'})
        self.assertEqual(reply.status_code, 409)

    def test_create_group_missing_name(self):
        reply = client.post('/api/groups/', self.f['local_user1'], {})
        self.assertEqual(reply.status_code, 400)

    def test_list_groups(self):
        self.prepare_groups()
        reply = client.get('/api/groups/', self.f['local_user1'])
        self.assertEqual(reply.status_code, 200)
        self.assertEqual(len(reply.json()), 1)
        reply2 = client.get('/api/groups/', self.f['local_user2'])
        self.assertEqual(reply2.status_code, 200)
        self.assertEqual(len(reply2.json()), 0)

    def test_group_detail_member(self):
        self.prepare_groups()
        reply = client.get('/api/groups/{}/'.format(self.f['group1'].id), self.f['local_user1'])
        self.assertEqual(reply.status_code, 200)
        self.assertEqual(reply.json()['handle'], str(self.f['group1']))
        self.assertEqual(len(reply.json()['members']), 1)

    def test_group_detail_non_member(self):
        self.prepare_groups()
        reply = client.get('/api/groups/{}/'.format(self.f['group1'].id), self.f['local_user2'])
        self.assertEqual(reply.status_code, 404)

    def test_remove_member(self):
        self.prepare_groups()
        self.f['group1'].members.add(self.f['local_user2'].public_identity)
        reply = client.delete('/api/groups/{}/members/{}/'.format(
            self.f['group1'].id, self.f['local_user2'].public_identity.id), self.f['local_user1'])
        self.assertEqual(reply.status_code, 204)
        self.assertEqual(self.f['group1'].members.count(), 1)

    def test_remove_last_member_blocked(self):
        self.prepare_groups()
        reply = client.delete('/api/groups/{}/members/{}/'.format(
            self.f['group1'].id, self.f['local_user1'].public_identity.id), self.f['local_user1'])
        self.assertEqual(reply.status_code, 400)
        self.assertEqual(self.f['group1'].members.count(), 1)

    def test_remove_member_non_member_denied(self):
        self.prepare_groups()
        self.f['group1'].members.add(self.f['local_user2'].public_identity)
        reply = client.delete('/api/groups/{}/members/{}/'.format(
            self.f['group1'].id, self.f['local_user1'].public_identity.id), self.f['ext_user1'])
        self.assertEqual(reply.status_code, 404)
        self.assertEqual(self.f['group1'].members.count(), 2)


class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
    def setUp(self):
        super().setUp()
        self.prepare_users()
        self.prepare_groups()

    def test_invite_local_member_full_flow(self):
        group = self.f['group1']
        inviter = self.f['local_user1']
        invitee = self.f['local_user2']

        reply1 = client.post('/api/groups/{}/invites/'.format(group.id), inviter, {'invitee': str(invitee)})
        self.assertEqual(reply1.status_code, 201)
        secret = reply1.json()['secret']
        self.assertEqual(GroupInvite.objects.count(), 1)

        reply2 = client.post('/api/groupinvites/', inviter, {
            'group': str(group), 'inviter': str(inviter), 'inviter_key': inviter.public_key(),
            'invitee': str(invitee), 'secret': secret
        })
        self.assertEqual(reply2.status_code, 201)
        incoming = GroupInviteIncoming.objects.get()
        self.assertEqual(incoming.invitee_user, invitee)

        reply3 = client.get('/api/groupinvites/', invitee)
        self.assertEqual(reply3.status_code, 200)
        self.assertEqual(len(reply3.json()), 1)
        self.assertEqual(reply3.json()[0]['group'], str(group))
        self.assertEqual(reply3.json()[0]['inviter'], str(inviter))

        reply4 = client.post('/api/group_invites/accept/', invitee, {
            'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
        })
        self.assertEqual(reply4.status_code, 201)
        self.assertTrue(group.is_member(invitee.public_identity))
        self.assertEqual(GroupInvite.objects.count(), 0)

        reply5 = client.delete('/api/groupinvites/{}/'.format(incoming.id), invitee)
        self.assertEqual(reply5.status_code, 204)
        self.assertEqual(GroupInviteIncoming.objects.count(), 0)

    def test_invite_non_member_denied(self):
        group = self.f['group1']
        reply = client.post('/api/groups/{}/invites/'.format(group.id), self.f['local_user2'],
                            {'invitee': str(self.f['ext_user1'])})
        self.assertEqual(reply.status_code, 404)
        self.assertEqual(GroupInvite.objects.count(), 0)

    def test_invite_already_member(self):
        group = self.f['group1']
        group.members.add(self.f['local_user2'].public_identity)
        reply = client.post('/api/groups/{}/invites/'.format(group.id), self.f['local_user1'],
                            {'invitee': str(self.f['local_user2'])})
        self.assertEqual(reply.status_code, 208)

    def test_decline_invite(self):
        invite = GroupInviteIncoming.objects.create(
            group_name=self.f['group1'].name, group_domain=self.f['group1'].domain,
            inviter_username=self.f['local_user1'].username, inviter_domain=self.f['local_user1'].domain,
            invitee_user=self.f['local_user2'], secret='some-secret')
        reply = client.delete('/api/groupinvites/{}/'.format(invite.id), self.f['local_user2'])
        self.assertEqual(reply.status_code, 204)
        self.assertEqual(GroupInviteIncoming.objects.count(), 0)

    def test_accept_wrong_secret(self):
        group = self.f['group1']
        inviter = self.f['local_user1']
        invitee = self.f['local_user2']
        client.post('/api/groups/{}/invites/'.format(group.id), inviter, {'invitee': str(invitee)})
        reply = client.post('/api/group_invites/accept/', invitee, {
            'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(),
            'secret': 'not-the-right-secret'
        })
        self.assertEqual(reply.status_code, 404)
        self.assertFalse(group.is_member(invitee.public_identity))

    def test_accept_remote_member(self):
        group = self.f['group1']
        inviter = self.f['local_user1']
        invitee = DummyExternalUser('newmember', 'remote.example', known=False)
        reply1 = client.post('/api/groups/{}/invites/'.format(group.id), inviter, {'invitee': str(invitee)})
        secret = reply1.json()['secret']

        reply = client.post('/api/group_invites/accept/', invitee, {
            'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
        })
        self.assertEqual(reply.status_code, 201)
        identity = KnownIdentity.objects.get(username='newmember', domain='remote.example')
        self.assertTrue(group.is_member(identity))

    def test_accept_bad_signature(self):
        group = self.f['group1']
        inviter = self.f['local_user1']
        invitee = self.f['local_user2']
        reply1 = client.post('/api/groups/{}/invites/'.format(group.id), inviter, {'invitee': str(invitee)})
        secret = reply1.json()['secret']
        bad_signature_client = SignatureAuthClient(bad_signature=True)
        reply = bad_signature_client.post('/api/group_invites/accept/', invitee, {
            'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
        })
        self.assertEqual(reply.status_code, 401)
        self.assertFalse(group.is_member(invitee.public_identity))
