from __future__ import unicode_literals
import copy

from django import forms
from django.conf import settings
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
from django.test import TestCase
from django.test.client import Client
from django.urls import reverse

from guardian.admin import GuardedModelAdmin
from guardian.compat import str
from guardian.shortcuts import get_perms
from guardian.shortcuts import get_perms_for_model
from guardian.models import Group
from guardian.testapp.tests.conf import skipUnlessTestApp
from guardian.testapp.models import LogEntryWithGroup as LogEntry

User = get_user_model()


class ContentTypeGuardedAdmin(GuardedModelAdmin):
    pass

try:
    admin.site.unregister(ContentType)
except admin.sites.NotRegistered:
    pass
admin.site.register(ContentType, ContentTypeGuardedAdmin)


class AdminTests(TestCase):

    def setUp(self):
        self.admin = User.objects.create_superuser('admin', 'admin@example.com',
                                                   'admin')
        self.user = User.objects.create_user('joe', 'joe@example.com', 'joe')
        self.group = Group.objects.create(name='group')
        self.client = Client()
        self.obj = ContentType.objects.create(
            model='bar', app_label='fake-for-guardian-tests')
        self.obj_info = self.obj._meta.app_label, self.obj._meta.model_name

    def tearDown(self):
        self.client.logout()

    def _login_superuser(self):
        self.client.login(username='admin', password='admin')

    def test_view_manage_wrong_obj(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info,
                      kwargs={'object_pk': -10, 'user_id': self.user.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_view(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['object'], self.obj)

    def test_view_manage_wrong_user(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info,
                      kwargs={'object_pk': self.obj.pk, 'user_id': -10})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_view_manage_user_form(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'user': self.user.username, 'submit_manage_user': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)
        redirect_url = reverse('admin:%s_%s_permissions_manage_user' %
                               self.obj_info, kwargs={'object_pk': self.obj.pk,
                                                      'user_id': self.user.pk})
        self.assertEqual(response.request['PATH_INFO'], redirect_url)

    def test_view_manage_negative_user_form(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        self.user = User.objects.create(username='negative_id_user', pk=-2010)
        data = {'user': self.user.username, 'submit_manage_user': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)
        redirect_url = reverse('admin:%s_%s_permissions_manage_user' %
                               self.obj_info, args=[self.obj.pk, self.user.pk])
        self.assertEqual(response.request['PATH_INFO'], redirect_url)

    def test_view_manage_user_form_wrong_user(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'user': 'wrong-user', 'submit_manage_user': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('user' in response.context['user_form'].errors)

    def test_view_manage_user_form_wrong_field(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'user': '<xss>', 'submit_manage_user': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('user' in response.context['user_form'].errors)

    def test_view_manage_user_form_empty_user(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'user': '', 'submit_manage_user': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('user' in response.context['user_form'].errors)

    def test_view_manage_user_wrong_perms(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info,
                      args=[self.obj.pk, self.user.pk])
        perms = ['change_user']  # This is not self.obj related permission
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('permissions' in response.context['form'].errors)

    def test_view_manage_user(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info,
                      args=[self.obj.pk, self.user.pk])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        choices = set([c[0] for c in
                       response.context['form'].fields['permissions'].choices])
        self.assertEqual(
            set([p.codename for p in get_perms_for_model(self.obj)]),
            choices,
        )

        # Add some perms and check if changes were persisted
        perms = ['change_%s' % self.obj_info[
            1], 'delete_%s' % self.obj_info[1]]
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)
        self.assertIn('selected', str(response.context['form']))
        self.assertEqual(
            set(get_perms(self.user, self.obj)),
            set(perms),
        )

        # Remove perm and check if change was persisted
        perms = ['change_%s' % self.obj_info[1]]
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)

        self.assertEqual(
            set(get_perms(self.user, self.obj)),
            set(perms),
        )

    def test_view_manage_group_form(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'group': self.group.name, 'submit_manage_group': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)
        redirect_url = reverse('admin:%s_%s_permissions_manage_group' %
                               self.obj_info, args=[self.obj.pk, self.group.id])
        self.assertEqual(response.request['PATH_INFO'], redirect_url)

    def test_view_manage_negative_group_form(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        self.group = Group.objects.create(name='neagive_id_group', id=-2010)
        data = {'group': self.group.name, 'submit_manage_group': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)
        redirect_url = reverse('admin:%s_%s_permissions_manage_group' %
                               self.obj_info, args=[self.obj.pk, self.group.id])
        self.assertEqual(response.request['PATH_INFO'], redirect_url)

    def test_view_manage_group_form_wrong_group(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'group': 'wrong-group', 'submit_manage_group': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('group' in response.context['group_form'].errors)

    def test_view_manage_group_form_wrong_field(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'group': '<xss>', 'submit_manage_group': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('group' in response.context['group_form'].errors)

    def test_view_manage_group_form_empty_group(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions' % self.obj_info,
                      args=[self.obj.pk])
        data = {'group': '', 'submit_manage_group': 'submit'}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 0)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('group' in response.context['group_form'].errors)

    def test_view_manage_group_wrong_perms(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_group' %
                      self.obj_info, args=[self.obj.pk, self.group.id])
        perms = ['change_user']  # This is not self.obj related permission
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertTrue('permissions' in response.context['form'].errors)

    def test_view_manage_group(self):
        self._login_superuser()
        url = reverse('admin:%s_%s_permissions_manage_group' %
                      self.obj_info, args=[self.obj.pk, self.group.id])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        choices = set([c[0] for c in
                       response.context['form'].fields['permissions'].choices])
        self.assertEqual(
            set([p.codename for p in get_perms_for_model(self.obj)]),
            choices,
        )

        # Add some perms and check if changes were persisted
        perms = ['change_%s' % self.obj_info[
            1], 'delete_%s' % self.obj_info[1]]
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)

        self.assertEqual(
            set(get_perms(self.group, self.obj)),
            set(perms),
        )

        # Remove perm and check if change was persisted
        perms = ['delete_%s' % self.obj_info[1]]
        data = {'permissions': perms}
        response = self.client.post(url, data, follow=True)
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertEqual(response.redirect_chain[0][1], 302)

        self.assertEqual(
            set(get_perms(self.group, self.obj)),
            set(perms),
        )

if 'django.contrib.admin' not in settings.INSTALLED_APPS:
    # Skip admin tests if admin app is not registered
    # we simpy clean up AdminTests class ...
    # TODO: use @unittest.skipUnless('django.contrib.admin' in settings.INSTALLED_APPS)
    #       if possible (requires Python 2.7, though)
    AdminTests = type('AdminTests', (TestCase,), {})  # pyflakes:ignore


@skipUnlessTestApp
class GuardedModelAdminTests(TestCase):

    def _get_gma(self, attrs=None, name=None, model=None):
        """
        Returns ``GuardedModelAdmin`` instance.
        """
        attrs = attrs or {}
        name = str(name or 'GMA')
        model = model or User
        GMA = type(name, (GuardedModelAdmin,), attrs)
        gma = GMA(model, admin.site)
        return gma

    def test_obj_perms_manage_template_attr(self):
        attrs = {'obj_perms_manage_template': 'foobar.html'}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(gma.get_obj_perms_manage_template(), 'foobar.html')

    def test_obj_perms_manage_user_template_attr(self):
        attrs = {'obj_perms_manage_user_template': 'foobar.html'}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(
            gma.get_obj_perms_manage_user_template(), 'foobar.html')

    def test_obj_perms_manage_user_form_attr(self):
        attrs = {'obj_perms_manage_user_form': forms.Form}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(issubclass(gma.get_obj_perms_manage_user_form(None), forms.Form))

    def test_obj_perms_user_select_form_attr(self):
        attrs = {'obj_perms_user_select_form': forms.Form}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(issubclass(gma.get_obj_perms_user_select_form(None), forms.Form))

    def test_obj_perms_manage_group_template_attr(self):
        attrs = {'obj_perms_manage_group_template': 'foobar.html'}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(gma.get_obj_perms_manage_group_template(),
                        'foobar.html')

    def test_obj_perms_manage_group_form_attr(self):
        attrs = {'obj_perms_manage_group_form': forms.Form}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(issubclass(gma.get_obj_perms_manage_group_form(None), forms.Form))

    def test_obj_perms_group_select_form_attr(self):
        attrs = {'obj_perms_group_select_form': forms.Form}
        gma = self._get_gma(attrs=attrs)
        self.assertTrue(issubclass(gma.get_obj_perms_group_select_form(None), forms.Form))

    def test_user_can_acces_owned_objects_only(self):
        attrs = {
            'user_can_access_owned_objects_only': True,
            'user_owned_objects_field': 'user',
        }
        gma = self._get_gma(attrs=attrs, model=LogEntry)
        joe = User.objects.create_user('joe', 'joe@example.com', 'joe')
        jane = User.objects.create_user('jane', 'jane@example.com', 'jane')
        ctype = ContentType.objects.get_for_model(User)
        joe_entry = LogEntry.objects.create(user=joe, content_type=ctype,
                                            object_id=joe.pk, action_flag=1, change_message='foo')
        LogEntry.objects.create(user=jane, content_type=ctype,
                                object_id=jane.pk, action_flag=1, change_message='bar')
        request = HttpRequest()
        request.user = joe
        qs = gma.get_queryset(request)
        self.assertEqual([e.pk for e in qs], [joe_entry.pk])

    def test_user_can_acces_owned_objects_only_unless_superuser(self):
        attrs = {
            'user_can_access_owned_objects_only': True,
            'user_owned_objects_field': 'user',
        }
        gma = self._get_gma(attrs=attrs, model=LogEntry)
        joe = User.objects.create_superuser('joe', 'joe@example.com', 'joe')
        jane = User.objects.create_user('jane', 'jane@example.com', 'jane')
        ctype = ContentType.objects.get_for_model(User)
        joe_entry = LogEntry.objects.create(user=joe, content_type=ctype,
                                            object_id=joe.pk, action_flag=1, change_message='foo')
        jane_entry = LogEntry.objects.create(user=jane, content_type=ctype,
                                             object_id=jane.pk, action_flag=1, change_message='bar')
        request = HttpRequest()
        request.user = joe
        qs = gma.get_queryset(request)
        self.assertEqual(sorted([e.pk for e in qs]),
                         sorted([joe_entry.pk, jane_entry.pk]))

    def test_user_can_access_owned_by_group_objects_only(self):
        attrs = {
            'user_can_access_owned_by_group_objects_only': True,
            'group_owned_objects_field': 'group',
        }
        gma = self._get_gma(attrs=attrs, model=LogEntry)
        joe = User.objects.create_user('joe', 'joe@example.com', 'joe')
        joe_group = Group.objects.create(name='joe-group')
        joe.groups.add(joe_group)
        jane = User.objects.create_user('jane', 'jane@example.com', 'jane')
        jane_group = Group.objects.create(name='jane-group')
        jane.groups.add(jane_group)
        ctype = ContentType.objects.get_for_model(User)
        LogEntry.objects.create(user=joe, content_type=ctype,
                                object_id=joe.pk, action_flag=1, change_message='foo')
        LogEntry.objects.create(user=jane, content_type=ctype,
                                object_id=jane.pk, action_flag=1, change_message='bar')
        joe_entry_group = LogEntry.objects.create(user=jane, content_type=ctype,
                                                  object_id=joe.pk, action_flag=1, change_message='foo',
                                                  group=joe_group)
        request = HttpRequest()
        request.user = joe
        qs = gma.get_queryset(request)
        self.assertEqual([e.pk for e in qs], [joe_entry_group.pk])

    def test_user_can_access_owned_by_group_objects_only_unless_superuser(self):
        attrs = {
            'user_can_access_owned_by_group_objects_only': True,
            'group_owned_objects_field': 'group',
        }
        gma = self._get_gma(attrs=attrs, model=LogEntry)
        joe = User.objects.create_superuser('joe', 'joe@example.com', 'joe')
        joe_group = Group.objects.create(name='joe-group')
        joe.groups.add(joe_group)
        jane = User.objects.create_user('jane', 'jane@example.com', 'jane')
        jane_group = Group.objects.create(name='jane-group')
        jane.groups.add(jane_group)
        ctype = ContentType.objects.get_for_model(User)
        LogEntry.objects.create(user=joe, content_type=ctype,
                                object_id=joe.pk, action_flag=1, change_message='foo')
        LogEntry.objects.create(user=jane, content_type=ctype,
                                object_id=jane.pk, action_flag=1, change_message='bar')
        LogEntry.objects.create(user=jane, content_type=ctype,
                                object_id=joe.pk, action_flag=1, change_message='foo',
                                group=joe_group)
        LogEntry.objects.create(user=joe, content_type=ctype,
                                object_id=joe.pk, action_flag=1, change_message='foo',
                                group=jane_group)
        request = HttpRequest()
        request.user = joe
        qs = gma.get_queryset(request)
        self.assertEqual(sorted(e.pk for e in qs),
                         sorted(LogEntry.objects.values_list('pk', flat=True)))


class GrappelliGuardedModelAdminTests(TestCase):

    org_installed_apps = copy.copy(settings.INSTALLED_APPS)

    def _get_gma(self, attrs=None, name=None, model=None):
        """
        Returns ``GuardedModelAdmin`` instance.
        """
        attrs = attrs or {}
        name = str(name or 'GMA')
        model = model or User
        GMA = type(name, (GuardedModelAdmin,), attrs)
        gma = GMA(model, admin.site)
        return gma

    def setUp(self):
        settings.INSTALLED_APPS = ['grappelli'] + list(settings.INSTALLED_APPS)

    def tearDown(self):
        settings.INSTALLED_APPS = self.org_installed_apps

    def test_get_obj_perms_manage_template(self):
        gma = self._get_gma()
        self.assertEqual(gma.get_obj_perms_manage_template(),
                         'admin/guardian/contrib/grappelli/obj_perms_manage.html')

    def test_get_obj_perms_manage_user_template(self):
        gma = self._get_gma()
        self.assertEqual(gma.get_obj_perms_manage_user_template(),
                         'admin/guardian/contrib/grappelli/obj_perms_manage_user.html')

    def test_get_obj_perms_manage_group_template(self):
        gma = self._get_gma()
        self.assertEqual(gma.get_obj_perms_manage_group_template(),
                         'admin/guardian/contrib/grappelli/obj_perms_manage_group.html')
