import os
import smtplib
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.core import mail
from django.test import TestCase
from django.urls import reverse
from swapper import load_model

from .utils import (
    TestMultitenantAdminMixin,
    TestOrganizationMixin,
    TestUserAdditionalFieldsMixin,
)

Organization = load_model('openwisp_users', 'Organization')
OrganizationUser = load_model('openwisp_users', 'OrganizationUser')
User = get_user_model()
Group = load_model('openwisp_users', 'Group')


devnull = open(os.devnull, 'w')


class TestUsersAdmin(TestOrganizationMixin, TestUserAdditionalFieldsMixin, TestCase):
    """ test admin site """

    app_label = 'openwisp_users'

    def _get_edit_form_inline_params(self, user, organization):
        """
        This function is created to be overridden
        when the user extends openwisp-users
        and adds inline forms in the User model
        """
        return dict()

    @property
    def add_user_inline_params(self):
        return {
            'emailaddress_set-TOTAL_FORMS': 0,
            'emailaddress_set-INITIAL_FORMS': 0,
            'emailaddress_set-MIN_NUM_FORMS': 0,
            'emailaddress_set-MAX_NUM_FORMS': 0,
            f'{self.app_label}_organizationuser-TOTAL_FORMS': 0,
            f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
            f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
            f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 0,
        }

    def test_admin_add_user_auto_email(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
        queryset = User.objects.filter(username='testadd')
        self.assertEqual(queryset.count(), 1)
        user = queryset.first()
        self.assertEqual(user.emailaddress_set.count(), 1)
        emailaddress = user.emailaddress_set.first()
        self.assertEqual(emailaddress.email, 'test@testadd.com')
        self.assertEqual(len(mail.outbox), 1)

    def test_admin_add_user_empty_email(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testadd', email='', password1='tester', password2='tester'
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        response = self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
        queryset = User.objects.filter(username='testadd')
        self.assertEqual(queryset.count(), 0)
        self.assertContains(response, 'errors field-email')
        self.assertEqual(len(mail.outbox), 0)

    def test_admin_change_user_auto_email(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        user = self._create_user(username='changemailtest')
        params = user.__dict__
        params['email'] = 'new@mail.com'
        params.pop('phone_number')
        params.pop('_password')
        params.pop('last_login')
        params = self._additional_params_pop(params)
        # inline emails
        params.update(
            {
                'emailaddress_set-TOTAL_FORMS': 1,
                'emailaddress_set-INITIAL_FORMS': 1,
                'emailaddress_set-MIN_NUM_FORMS': 0,
                'emailaddress_set-MAX_NUM_FORMS': 0,
                'emailaddress_set-0-verified': True,
                'emailaddress_set-0-primary': True,
                'emailaddress_set-0-id': user.emailaddress_set.first().id,
                'emailaddress_set-0-user': user.id,
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 0,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 0,
            }
        )
        params.update(self._get_edit_form_inline_params(user, None))
        response = self.client.post(
            reverse(f'admin:{self.app_label}_user_change', args=[user.pk]),
            params,
            follow=True,
        )
        self.assertNotContains(response, 'error')
        user = User.objects.get(username='changemailtest')
        email_set = user.emailaddress_set
        self.assertEqual(email_set.count(), 2)
        self.assertEqual(email_set.filter(email='new@mail.com').count(), 1)
        self.assertEqual(len(mail.outbox), 1)

    def test_admin_change_user_email_empty(self):
        admin = self._create_admin(email='')
        self.client.force_login(admin)
        params = dict(
            username='testchange',
            email='',
            first_name='',
            last_name='',
            bio='',
            url='',
            company='',
            location='',
        )
        params.update(
            {
                'emailaddress_set-TOTAL_FORMS': 0,
                'emailaddress_set-INITIAL_FORMS': 0,
                'emailaddress_set-MIN_NUM_FORMS': 0,
                'emailaddress_set-MAX_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 0,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 0,
            }
        )
        params.update(self._additional_params_add())
        params.update(self._get_edit_form_inline_params(admin, None))
        response = self.client.post(
            reverse(f'admin:{self.app_label}_user_change', args=[admin.pk]), params
        )
        queryset = User.objects.filter(username='testchange')
        self.assertEqual(queryset.count(), 0)
        self.assertEqual(len(mail.outbox), 0)
        self.assertContains(response, 'errors field-email')

    def test_organization_view_on_site(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        org = self._create_org()
        response = self.client.get(
            reverse(f'admin:{self.app_label}_organization_change', args=[org.pk])
        )
        self.assertNotContains(response, 'viewsitelink')

    def test_organization_user_view_on_site(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        org = self._create_org()
        ou = org.add_user(admin)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_organizationuser_change', args=[ou.pk])
        )
        self.assertNotContains(response, 'viewsitelink')

    def test_admin_change_user_is_superuser_editable(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[admin.pk])
        )
        html = '<input type="checkbox" name="is_superuser"'
        self.assertContains(response, html)

    def test_admin_change_user_is_superuser_absent(self):
        operator = self._create_operator()
        options = {
            'organization': self._get_org(),
            'is_admin': True,
            'user': self._get_operator(),
        }
        self._create_org_user(**options)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[operator.pk])
        )
        html = (
            '<input type="checkbox" name="is_superuser" checked id="id_is_superuser">'
        )
        self.assertNotContains(response, html)

    def test_admin_change_user_permissions_editable(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[admin.pk])
        )
        html = '<select name="user_permissions"'
        self.assertContains(response, html)

    def test_admin_change_user_permissions_readonly(self):
        operator = self._create_operator()
        options = {
            'organization': self._get_org(),
            'is_admin': True,
            'user': self._get_operator(),
        }
        self._create_org_user(**options)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[operator.pk])
        )
        html = f'<div class="readonly">{self.app_label}'
        self.assertContains(response, html)

    def test_admin_changelist_user_superusers_hidden(self):
        self._create_admin()
        operator = self._create_operator()
        self.client.force_login(operator)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_changelist'))
        self.assertNotContains(response, 'admin</a>')

    def test_admin_changelist_operator_org_users_visible(self):
        # Check with operator in same organization and is_admin
        self._create_org_user()
        operator = self._create_operator()
        options = {'organization': self._get_org(), 'is_admin': True, 'user': operator}
        self._create_org_user(**options)
        self.client.force_login(operator)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_changelist'))
        self.assertContains(response, 'tester</a>')
        self.assertContains(response, 'operator</a>')

    def test_operator_changelist_superuser_column_hidden(self):
        operator = self._create_operator()
        options = {'organization': self._get_org(), 'is_admin': True, 'user': operator}
        self._create_org_user(**options)
        self.client.force_login(operator)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_changelist'))
        self.assertNotContains(response, 'Superuser status</a>')

    def test_operator_organization_member(self):
        org1 = self._create_org(name='operator-org1')
        org2 = self._create_org(name='operator-org2')
        operator = self._create_operator()
        options1 = {'organization': org1, 'is_admin': True, 'user': operator}
        options2 = {'organization': org2, 'is_admin': False, 'user': operator}
        self._create_org_user(**options1)
        self._create_org_user(**options2)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[operator.pk])
        )
        self.assertContains(response, 'selected>operator-org1</option>')
        self.assertContains(response, 'selected>operator-org2</option>')

    def test_operator_can_see_organization_add_user(self):
        org1 = self._create_org(name='operator-org1')
        org2 = self._create_org(name='operator-org2')
        operator = self._create_operator()
        org_permissions = Permission.objects.filter(
            codename__endswith='organization_user'
        )
        operator.user_permissions.add(*org_permissions)
        options1 = {'organization': org1, 'is_admin': True, 'user': operator}
        options2 = {'organization': org2, 'is_admin': False, 'user': operator}
        self._create_org_user(**options1)
        self._create_org_user(**options2)
        self.client.force_login(operator)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertContains(response, 'operator-org1</option>')
        self.assertNotContains(response, 'operator-org2</option>')

    def test_operator_change_organization(self):
        org1 = self._create_org(name='test-org1')
        org2 = self._create_org(name='test-org2')
        default_org = Organization.objects.get(name='default')
        operator = self._create_operator()
        org_permissions = Permission.objects.filter(
            codename__endswith='change_organization'
        )
        operator.user_permissions.add(*org_permissions)
        options1 = {'organization': org1, 'is_admin': True, 'user': operator}
        options2 = {'organization': org2, 'is_admin': False, 'user': operator}
        self._create_org_user(**options1)
        self._create_org_user(**options2)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_organization_change', args=[org1.pk])
        )
        self.assertContains(
            response, '<input type="text" name="name" value="{0}"'.format(org1.name)
        )
        response = self.client.get(
            reverse(
                f'admin:{self.app_label}_organization_change', args=[default_org.pk]
            )
        )
        self.assertContains(
            response,
            '<input type="text" name="name" value="{0}"'.format(default_org.name),
        )
        response = self.client.get(
            reverse(f'admin:{self.app_label}_organization_change', args=[org2.pk])
        )
        self.assertNotContains(
            response, '<input type="text" name="name" value="{0}"'.format(org2.name)
        )

    def test_operator_change_org_is_admin(self):
        org1 = self._create_org(name='test-org1')
        org2 = self._create_org(name='test-org2')
        operator = self._create_operator()
        org_permissions = Permission.objects.filter(
            codename__endswith='change_organization'
        )
        operator.user_permissions.add(*org_permissions)
        options1 = {'organization': org1, 'is_admin': True, 'user': operator}
        options2 = {'organization': org2, 'is_admin': False, 'user': operator}
        org_user1 = self._create_org_user(**options1)
        org_user2 = self._create_org_user(**options2)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(
                f'admin:{self.app_label}_organizationuser_change', args=[org_user1.pk]
            )
        )
        self.assertNotContains(
            response,
            '<input type="checkbox" name="is_admin" id="id_is_admin">'
            '<label class="vCheckboxLabel" for="id_is_admin">Is admin'
            '</label>',
        )
        response = self.client.get(
            reverse(
                f'admin:{self.app_label}_organizationuser_change', args=[org_user2.pk]
            )
        )
        self.assertNotContains(
            response,
            '<input type="checkbox" name="is_admin" id="id_is_admin">'
            '<label class="vCheckboxLabel" for="id_is_admin">Is admin'
            '</label>',
        )

    def test_admin_operator_delete_org_user(self):
        org1 = self._create_org(name='test-org1')
        org2 = self._create_org(name='test-org2')
        operator = self._create_operator()
        org_permissions = Permission.objects.filter(
            codename__endswith='organization_user'
        )
        operator.user_permissions.add(*org_permissions)
        options1 = {'organization': org1, 'is_admin': True, 'user': operator}
        options2 = {'organization': org2, 'is_admin': False, 'user': operator}
        org_user1 = self._create_org_user(**options1)
        org_user2 = self._create_org_user(**options2)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(
                f'admin:{self.app_label}_organizationuser_change', args=[org_user1.pk]
            )
        )
        self.assertContains(
            response,
            'class="deletelink-box">'
            f'<a href="/admin/{self.app_label}/organizationuser/{org_user1.pk}'
            '/delete/" class="deletelink">Delete',
        )
        response = self.client.get(
            reverse(
                f'admin:{self.app_label}_organizationuser_change', args=[org_user2.pk]
            )
        )
        self.assertNotContains(response, 'delete')

    def test_admin_changelist_superuser_column_visible(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_changelist'))
        self.assertContains(response, 'Superuser status</a>')

    def test_admin_operator_change_superuser_forbidden(self):
        admin = self._create_admin()
        operator = self._create_operator()
        options = {
            'organization': self._get_org(),
            'is_admin': True,
            'user': self._get_operator(),
        }
        self._create_org_user(**options)
        self.client.force_login(operator)
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[operator.pk])
        )
        self.assertEqual(response.status_code, 200)
        # operator trying to acess change form of superuser gets redirected
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[admin.pk])
        )
        self.assertEqual(response.status_code, 302)

    def test_new_user_email_exists(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
        res = self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
        self.assertContains(
            res, '<li>User with this Email address already exists.</li>'
        )

    def test_edit_user_email_exists(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        self._create_user()
        user = self._create_user(email='asd@asd.com', username='newTester')
        params = user.__dict__
        params['email'] = 'test@tester.com'
        params.pop('phone_number')
        params.pop('_password')
        params.pop('last_login')
        params = self._additional_params_pop(params)
        params.update(
            {
                'emailaddress_set-TOTAL_FORMS': 1,
                'emailaddress_set-INITIAL_FORMS': 1,
                'emailaddress_set-MIN_NUM_FORMS': 0,
                'emailaddress_set-MAX_NUM_FORMS': 0,
                'emailaddress_set-0-verified': True,
                'emailaddress_set-0-primary': True,
                'emailaddress_set-0-id': user.emailaddress_set.first().id,
                'emailaddress_set-0-user': user.id,
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 0,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 0,
            }
        )
        params.update(self._get_edit_form_inline_params(user, None))
        res = self.client.post(
            reverse(f'admin:{self.app_label}_user_change', args=[user.pk]),
            params,
            follow=True,
        )
        self.assertContains(
            res, '<li>User with this Email address already exists.</li>'
        )

    def test_change_staff_without_group(self):
        self.client.force_login(self._get_admin())
        user = self._create_operator()
        self._create_org_user(user=user)
        params = user.__dict__
        params.pop('_password')
        params.pop('last_login')
        params.pop('phone_number')
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(self._get_edit_form_inline_params(user, None))
        path = reverse(f'admin:{self.app_label}_user_change', args=[user.pk])
        r = self.client.post(path, params, follow=True)
        self.assertEqual(r.status_code, 200)
        self.assertContains(
            r, 'A staff user must belong to a group, please select one.'
        )
        user.refresh_from_db()
        self.assertEqual(user.groups.count(), 0)

    def test_change_staff_with_group(self):
        self.client.force_login(self._get_admin())
        user = self._create_operator()
        org = self._get_org()
        org.add_user(user)
        group = Group.objects.get(name='Administrator')
        params = user.__dict__
        params['groups'] = str(group.pk)
        params.pop('phone_number')
        params.pop('_password')
        params.pop('last_login')
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(self._get_edit_form_inline_params(user, org))
        path = reverse(f'admin:{self.app_label}_user_change', args=[user.pk])
        r = self.client.post(path, params, follow=True)
        self.assertEqual(r.status_code, 200)
        self.assertNotContains(r, 'error')
        user.refresh_from_db()
        self.assertEqual(user.groups.count(), 1)
        self.assertEqual(user.groups.get(name='Administrator').pk, group.pk)

    def test_admin_add_user_by_superuser(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        res = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertContains(res, 'is_superuser')

    def test_admin_add_user_by_operator(self):
        operator = self._create_operator()
        self.client.force_login(operator)
        res = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertNotContains(res, 'is_superuser')

    def test_admin_add_user_org_required(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
            is_staff=True,
            is_superuser=False,
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(
            {
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 1,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 1,
            }
        )
        res = self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
        queryset = User.objects.filter(username='testadd')
        self.assertEqual(queryset.count(), 0)
        self.assertContains(res, 'errors field-organization')

    def test_admin_user_add_form(self):
        self.client.force_login(self._get_admin())
        r = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertContains(r, 'first_name')
        self.assertContains(r, 'last_name')
        self.assertContains(r, 'phone_number')
        self.assertContains(r, 'groups')

    def test_add_staff_without_group(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        org = self._get_org()
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
            is_staff=True,
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(
            {
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 1,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 1,
                f'{self.app_label}_organizationuser-0-is_admin': 'on',
                f'{self.app_label}_organizationuser-0-organization': str(org.pk),
            }
        )
        res = self.client.post(
            reverse(f'admin:{self.app_label}_user_add'), params, follow=True
        )
        self.assertEqual(res.status_code, 200)
        self.assertContains(
            res, 'A staff user must belong to a group, please select one.'
        )
        user = User.objects.filter(username='testadd')
        self.assertEqual(user.count(), 0)

    def test_add_staff_with_group(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        group = Group.objects.get(name='Administrator')
        org = self._get_org()
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
            is_staff=True,
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(
            {
                'groups': str(group.pk),
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 1,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 1,
                f'{self.app_label}_organizationuser-0-is_admin': 'on',
                f'{self.app_label}_organizationuser-0-organization': str(org.pk),
            }
        )
        res = self.client.post(
            reverse(f'admin:{self.app_label}_user_add'), params, follow=True
        )
        self.assertEqual(res.status_code, 200)
        self.assertNotContains(res, 'error')
        user = User.objects.filter(username='testadd')
        self.assertEqual(user.count(), 1)

    def test_add_user_fieldsets(self):
        self.client.force_login(self._get_admin())
        r = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, 'Permissions')
        self.assertContains(r, 'Personal Info')

    def test_admin_add_superuser_org_not_required(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testadd',
            email='test@testadd.com',
            password1='tester',
            password2='tester',
            is_staff=True,
            is_superuser=True,
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        params.update(
            {
                f'{self.app_label}_organizationuser-TOTAL_FORMS': 1,
                f'{self.app_label}_organizationuser-INITIAL_FORMS': 0,
                f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
                f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 1,
            }
        )
        res = self.client.post(
            reverse(f'admin:{self.app_label}_user_add'), params, follow=True
        )
        self.assertNotContains(res, 'errors field-organization')
        self.assertNotContains(res, 'errors')
        queryset = User.objects.filter(username='testadd')
        self.assertEqual(queryset.count(), 1)
        user = queryset.first()
        self.assertTrue(user.is_superuser)
        self.assertTrue(user.is_staff)

    def test_operator_change_user_permissions(self):
        operator = self._create_operator()
        self.client.force_login(operator)
        admin = self._create_admin()
        response = self.client.get(
            reverse(f'admin:{self.app_label}_user_change', args=[admin.pk])
        )
        self.assertEqual(response.status_code, 302)

    def test_user_add_user(self):
        operator = self._create_operator()
        self.client.force_login(operator)
        # removing the "add_organizationuser" permission allows
        # achieving more test coverage
        add_organizationuser = Permission.objects.get(
            codename__endswith='add_organizationuser'
        )
        operator.user_permissions.remove(add_organizationuser)
        response = self.client.get(reverse(f'admin:{self.app_label}_user_add'))
        self.assertContains(response, '<input type="text" name="username"')

    def test_organization_owner(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        self._create_org_owner()
        response = self.client.get(
            reverse(f'admin:{self.app_label}_organizationowner_changelist')
        )
        self.assertContains(response, 'tester')

    def test_organization_uuid_field(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        response = self.client.get(reverse(f'admin:{self.app_label}_organization_add'))
        html = '<input type="text" name="name" value="default"'
        self.assertNotContains(response, html)

    def test_action_active(self):
        user = User.objects.create(
            username='openwisp',
            password='test',
            email='openwisp@test.com',
            is_active=False,
        )
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(self._get_admin())
        post_data = {
            '_selected_action': [user.pk],
            'action': 'make_active',
            'csrfmiddlewaretoken': 'test',
            'confirmation': 'Confirm',
        }
        response = self.client.post(path, post_data, follow=True)
        self.assertEqual(response.status_code, 200)

    def test_action_inactive(self):
        user = User.objects.create(
            username='openwisp',
            password='test',
            email='openwisp@test.com',
            is_active=True,
        )
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(self._get_admin())
        post_data = {
            '_selected_action': [user.pk],
            'action': 'make_inactive',
            'csrfmiddlewaretoken': 'test',
            'confirmation': 'Confirm',
        }
        response = self.client.post(path, post_data, follow=True)
        user.refresh_from_db()
        self.assertFalse(user.is_active)
        self.assertEqual(response.status_code, 200)

    def test_action_confirmation_page(self):
        user = User.objects.create(
            username='openwisp',
            password='test',
            email='openwisp@test.com',
            is_active=True,
        )
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(self._get_admin())
        post_data = {
            '_selected_action': [user.pk],
            'action': 'make_active',
            'csrfmiddlewaretoken': 'test',
        }
        response = self.client.post(path, post_data, follow=True)
        user.refresh_from_db()
        self.assertTrue(user.is_active)
        self.assertEqual(response.status_code, 200)

    def test_superuser_delete_operator(self):
        user = self._create_operator()
        org = self._create_org()
        org_user = self._create_org_user(user=user, organization=org, is_admin=True)
        post_data = {
            '_selected_action': [user.pk],
            'action': 'delete_selected',
            'post': 'yes',
        }
        self.client.force_login(self._get_admin())
        path = reverse(f'admin:{self.app_label}_user_changelist')
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.filter(pk=user.pk)
        org_user_qs = OrganizationUser.objects.filter(pk=org_user.pk)
        self.assertEqual(r.status_code, 200)
        self.assertEqual(user_qs.count(), 0)
        self.assertEqual(org_user_qs.count(), 0)

    def test_staff_delete_staff(self):
        org = self._create_org()
        staff = self._create_user(
            username='staff', is_staff=True, email='staff@gmail.com'
        )
        group = Group.objects.filter(name='Administrator')
        staff.groups.set(group)
        self._create_org_user(organization=org, user=staff, is_admin=True)
        op = self._create_operator()
        op.groups.set(group)
        self._create_org_user(organization=org, user=op, is_admin=True)
        post_data = {
            '_selected_action': [op.pk],
            'action': 'delete_selected_overridden',
            'post': 'yes',
        }
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(staff)
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.filter(pk=op.pk)
        self.assertEqual(r.status_code, 200)
        self.assertEqual(user_qs.count(), 0)
        self.assertContains(r, 'Successfully deleted 1 user')

    def test_superuser_delete_staff(self):
        org = self._create_org()
        group = Group.objects.filter(name='Administrator')
        op = self._create_operator()
        op.groups.set(group)
        self._create_org_user(organization=org, user=op, is_admin=True)
        post_data = {
            '_selected_action': [op.pk],
            'action': 'delete_selected_overridden',
            'post': 'yes',
        }
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(self._get_admin())
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.filter(pk=op.pk)
        self.assertEqual(r.status_code, 200)
        self.assertEqual(user_qs.count(), 0)
        self.assertContains(r, 'Successfully deleted 1 user')

    def test_staff_delete_org_owner(self):
        org = self._create_org()
        staff = self._create_user(
            username='staff', is_staff=True, email='staff@gmail.com'
        )
        group = Group.objects.filter(name='Administrator')
        staff.groups.set(group)
        op = self._create_operator()
        op.groups.set(group)
        org_user = self._create_org_user(organization=org, user=op, is_admin=True)
        self._create_org_owner(organization_user=org_user, organization=org)
        self._create_org_user(organization=org, user=staff, is_admin=True)
        path = reverse(f'admin:{self.app_label}_user_changelist')
        post_data = {
            'action': 'delete_selected_overridden',
            '_selected_action': [op.pk],
            'post': 'yes',
        }
        self.client.force_login(staff)
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.filter(pk=op.pk)
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, f"delete 1 organization owner: {op.username}")
        self.assertEqual(user_qs.count(), 1)

    def test_superuser_delete_org_owner(self):
        org = self._create_org()
        group = Group.objects.filter(name='Administrator')
        op = self._create_operator()
        op.groups.set(group)
        org_user = self._create_org_user(organization=org, user=op, is_admin=True)
        self._create_org_owner(organization_user=org_user, organization=org)
        path = reverse(f'admin:{self.app_label}_user_changelist')
        post_data = {
            'action': 'delete_selected_overridden',
            '_selected_action': [op.pk],
            'post': 'yes',
        }
        self.client.force_login(self._get_admin())
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.filter(pk=op.pk)
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, 'Successfully deleted 1 user')
        self.assertEqual(user_qs.count(), 0)

    def test_staff_bulk_delete(self):
        org = self._create_org()
        group = Group.objects.filter(name='Administrator')
        staff = self._create_user(
            username='staff', is_staff=True, email='staff@gmail.com'
        )
        staff.groups.set(group)
        op1 = self._create_user(username='op1', is_staff=True, email='op1@gmail.com')
        op2 = self._create_user(username='op2', is_staff=True, email='op2@gmail.com')
        op1.groups.set(group)
        op2.groups.set(group)
        org_user = self._create_org_user(organization=org, user=op1, is_admin=True)
        self._create_org_owner(organization_user=org_user, organization=org)
        self._create_org_user(organization=org, user=op2, is_admin=True)
        self._create_org_user(organization=org, user=staff, is_admin=True)
        post_data = {
            'action': 'delete_selected_overridden',
            '_selected_action': [op1.pk, op2.pk],
        }
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(staff)
        r = self.client.post(path, post_data, follow=True)
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, f"delete 1 organization owner: {op1.username}")
        post_data.update({'post': 'yes'})
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.all()
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, 'Successfully deleted 1 user')
        self.assertEqual(user_qs.count(), 3)
        self.assertEqual(user_qs.filter(pk=op2.pk).count(), 0)
        self.assertEqual(user_qs.filter(pk=op1.pk).count(), 1)

    def test_superuser_bulk_delete(self):
        org = self._create_org()
        group = Group.objects.filter(name='Administrator')
        op1 = self._create_user(username='op1', is_staff=True, email='op1@gmail.com')
        op2 = self._create_user(username='op2', is_staff=True, email='op2@gmail.com')
        op1.groups.set(group)
        op2.groups.set(group)
        org_user = self._create_org_user(organization=org, user=op1, is_admin=True)
        self._create_org_owner(organization_user=org_user, organization=org)
        self._create_org_user(organization=org, user=op2, is_admin=True)
        post_data = {
            'action': 'delete_selected_overridden',
            '_selected_action': [op1.pk, op2.pk],
            'post': 'yes',
        }
        path = reverse(f'admin:{self.app_label}_user_changelist')
        self.client.force_login(self._get_admin())
        r = self.client.post(path, post_data, follow=True)
        user_qs = User.objects.all()
        self.assertEqual(r.status_code, 200)
        self.assertContains(r, 'Successfully deleted 2 users')
        self.assertEqual(user_qs.count(), 2)

    @patch('sys.stdout', devnull)
    @patch('sys.stderr', devnull)
    def test_admin_add_user_with_invalid_email(self):
        admin = self._create_admin()
        self.client.force_login(admin)
        params = dict(
            username='testmail',
            email='test@invalid.com',
            password1='tester',
            password2='tester',
        )
        params.update(self.add_user_inline_params)
        params.update(self._additional_params_add())
        with patch('allauth.account.models.EmailAddress.objects.add_email') as mocked:
            mocked.side_effect = smtplib.SMTPSenderRefused(
                501, '5.1.7 Bad sender address syntax', 'test_name@test_domain'
            )
            self.client.post(reverse(f'admin:{self.app_label}_user_add'), params)
            mocked.assert_called_once()

    @classmethod
    def tearDownClass(cls):
        devnull.close()


class TestBasicUsersIntegration(
    TestOrganizationMixin, TestUserAdditionalFieldsMixin, TestCase
):
    """
    tests basic integration with openwisp_users
    (designed to be inherited in other openwisp modules)
    """

    app_label = 'openwisp_users'

    def _get_edit_form_inline_params(self, user, organization):
        organization_user = OrganizationUser.objects.get(
            user=user, organization=organization
        )
        return {
            # email address inline
            'emailaddress_set-TOTAL_FORMS': 1,
            'emailaddress_set-INITIAL_FORMS': 1,
            'emailaddress_set-MIN_NUM_FORMS': 0,
            'emailaddress_set-MAX_NUM_FORMS': 0,
            'emailaddress_set-0-verified': True,
            'emailaddress_set-0-primary': True,
            'emailaddress_set-0-id': user.emailaddress_set.first().id,
            'emailaddress_set-0-user': user.id,
            # organization user inline
            f'{self.app_label}_organizationuser-TOTAL_FORMS': 1,
            f'{self.app_label}_organizationuser-INITIAL_FORMS': 1,
            f'{self.app_label}_organizationuser-MIN_NUM_FORMS': 0,
            f'{self.app_label}_organizationuser-MAX_NUM_FORMS': 1000,
            f'{self.app_label}_organizationuser-0-is_admin': False,
            f'{self.app_label}_organizationuser-0-organization': str(organization.pk),
            f'{self.app_label}_organizationuser-0-id': str(organization_user.pk),
            f'{self.app_label}_organizationuser-0-user': str(user.pk),
        }

    def test_change_user(self):
        admin = self._create_admin()
        user = self._create_user()
        org = Organization.objects.first()
        org.add_user(user)
        self.client.force_login(admin)
        params = user.__dict__
        params['bio'] = 'Test change'
        params.pop('phone_number')
        params.pop('_password')
        params.pop('last_login')
        params = self._additional_params_pop(params)
        params.update(self._get_edit_form_inline_params(user, org))
        response = self.client.post(
            reverse(f'admin:{self.app_label}_user_change', args=[user.pk]),
            params,
            follow=True,
        )
        self.assertNotContains(response, 'error')
        user.refresh_from_db()
        self.assertEqual(user.bio, params['bio'])


class TestMultitenantAdmin(TestMultitenantAdminMixin, TestOrganizationMixin, TestCase):
    app_label = 'openwisp_users'

    def _create_multitenancy_test_env(self):
        org1 = self._create_org(name='organization1')
        org2 = self._create_org(name='organization2')
        org3 = self._create_org(name='organization3')
        user1 = self._create_user(username='user1', email='user1j@something.com')
        user12 = self._create_user(username='user12', email='user12j@something.com')
        user2 = self._create_user(username='user2', email='user2j@something.com')
        user22 = self._create_user(username='user22', email='user22j@something.com')
        user23 = self._create_user(
            username='user23', email='user23j@something.com', is_superuser=True
        )
        user3 = self._create_user(username='user3', email='user3@something.com',)
        organization_user1 = self._create_org_user(organization=org1, user=user1)
        organization_user12 = self._create_org_user(organization=org1, user=user12)
        organization_user2 = self._create_org_user(organization=org2, user=user2)
        organization_user22 = self._create_org_user(organization=org2, user=user22)
        organization_owner1 = self._create_org_owner(
            organization_user=organization_user1, organization=org1
        )
        organization_owner2 = self._create_org_owner(
            organization_user=organization_user2, organization=org2
        )
        operator = self._create_operator()
        organization_user3 = self._create_org_user(
            organization=org3, user=operator, is_admin=True
        )
        organization_user31 = self._create_org_user(organization=org3, user=user3,)
        organization_user1o = self._create_org_user(organization=org1, user=operator,)
        data = dict(
            org1=org1,
            org2=org2,
            org3=org3,
            user1=user1,
            user2=user2,
            user12=user12,
            user22=user22,
            user23=user23,
            user3=user3,
            organization_user1=organization_user1,
            organization_user2=organization_user2,
            organization_user12=organization_user12,
            organization_user22=organization_user22,
            organization_user3=organization_user3,
            organization_user1o=organization_user1o,
            organization_user31=organization_user31,
            organization_owner1=organization_owner1,
            organization_owner2=organization_owner2,
            operator=operator,
        )
        return data

    def test_multitenancy_organization_user_queryset(self):
        data = self._create_multitenancy_test_env()
        self._test_multitenant_admin(
            url=reverse(f'admin:{self.app_label}_organizationuser_changelist'),
            hidden=[
                data['organization_user2'].user.username,
                data['organization_user22'].user.username,
            ],
            visible=[
                data['organization_user1'].user.username,
                data['organization_user12'].user.username,
                data['organization_user1o'].user.username,
                data['organization_user3'].user.username,
            ],
        )

    def test_multitenancy_organization_owner_queryset(self):
        data = self._create_multitenancy_test_env()
        self._test_multitenant_admin(
            url=reverse(f'admin:{self.app_label}_organizationowner_changelist'),
            hidden=[data['organization_owner2'].organization_user.user.username],
            visible=[data['organization_owner1'].organization_user.user.username],
        )

    def test_useradmin_specific_multitenancy_costraints(self):
        data = self._create_multitenancy_test_env()
        self._test_multitenant_admin(
            url=reverse(f'admin:{self.app_label}_user_changelist'),
            visible=[data['user3'], data['operator']],
            hidden=[data['user2'], data['user22'], data['user1'], data['user12']],
        )