from unittest.mock import call

import pytest
from django.contrib.auth import get_user_model
from django.core import mail

from ... import BANS_CACHE
from ....cache.test import assert_invalidates_cache
from ....test import assert_contains, assert_has_error_message, assert_not_contains
from ...datadownloads import request_user_data_download
from ...models import Ban, DataDownload, DeletedUser
from ...test import create_test_user

User = get_user_model()


def create_multiple_users(**kwargs):
    return [
        create_test_user("User%s" % i, "user%s@gmail.com" % i, **kwargs)
        for i in range(5)
    ]


def get_multiple_users_ids(**kwargs):
    users = create_multiple_users(**kwargs)
    return [u.id for u in users]


@pytest.fixture
def users_ids(db):
    return get_multiple_users_ids()


def test_multiple_users_can_be_activated_with_mass_action(
    admin_client, users_admin_link
):
    users = create_multiple_users(requires_activation=1)
    response = admin_client.post(
        users_admin_link,
        data={"action": "activate", "selected_items": [u.id for u in users]},
    )

    for user in users:
        user.refresh_from_db()
        assert not user.requires_activation


def test_activating_multiple_users_sends_email_notifications_to_them(
    admin_client, users_admin_link
):
    users_ids = get_multiple_users_ids(requires_activation=1)
    response = admin_client.post(
        users_admin_link, data={"action": "activate", "selected_items": users_ids}
    )

    assert len(mail.outbox) == len(users_ids)
    assert "has been activated" in mail.outbox[0].subject


def test_ban_multiple_users_form_is_rendered(admin_client, users_admin_link):
    users_ids = get_multiple_users_ids()
    response = admin_client.post(
        users_admin_link, data={"action": "ban", "selected_items": users_ids}
    )
    assert response.status_code == 200


def test_multiple_users_can_be_banned_with_mass_action(admin_client, users_admin_link):
    users = create_multiple_users()
    admin_client.post(
        users_admin_link,
        data={
            "action": "ban",
            "selected_items": [u.id for u in users],
            "ban_type": ["usernames", "emails", "domains"],
            "finalize": "",
        },
    )

    for user in users:
        Ban.objects.get(banned_value=user.username.lower())
        Ban.objects.get(banned_value=user.email)
        Ban.objects.get(banned_value="*%s" % user.email[-10:])


def test_option_to_ban_multiple_users_ips_is_disabled_if_user_ips_are_not_available(
    admin_client, users_admin_link, users_ids
):
    response = admin_client.post(
        users_admin_link, data={"action": "ban", "selected_items": users_ids}
    )
    assert_not_contains(response, 'value="ip"')
    assert_not_contains(response, 'value="ip_first"')
    assert_not_contains(response, 'value="ip_two"')


def test_option_to_ban_multiple_users_ips_is_enabled_if_user_ips_are_available(
    admin_client, users_admin_link
):
    users_ids = get_multiple_users_ids(joined_from_ip="1.2.3.4")
    response = admin_client.post(
        users_admin_link, data={"action": "ban", "selected_items": users_ids}
    )
    assert_contains(response, 'value="ip"')
    assert_contains(response, 'value="ip_first"')
    assert_contains(response, 'value="ip_two"')


def test_multiple_users_ips_can_be_banned_with_mass_action(
    admin_client, users_admin_link
):
    users_ids = get_multiple_users_ids(joined_from_ip="1.2.3.4")
    response = admin_client.post(
        users_admin_link,
        data={
            "action": "ban",
            "selected_items": users_ids,
            "ban_type": ["ip", "ip_first", "ip_two"],
            "finalize": "",
        },
    )

    Ban.objects.get(banned_value="1.2.3.4")
    Ban.objects.get(banned_value="1.*")
    Ban.objects.get(banned_value="1.2.*")


def test_banning_multiple_users_with_mass_action_invalidates_bans_cache(
    admin_client, users_admin_link, users_ids
):
    with assert_invalidates_cache(BANS_CACHE):
        admin_client.post(
            users_admin_link,
            data={
                "action": "ban",
                "selected_items": users_ids,
                "ban_type": ["usernames", "emails", "domains"],
                "finalize": "",
            },
        )


def test_data_downloads_can_be_requested_for_multiple_users_with_mass_action(
    admin_client, users_admin_link
):
    users = create_multiple_users()
    response = admin_client.post(
        users_admin_link,
        data={
            "action": "request_data_download",
            "selected_items": [u.id for u in users],
        },
    )

    for user in users:
        DataDownload.objects.get(user=user)


def test_mass_action_is_not_requesting_data_downloads_for_users_with_existing_requests(
    admin_client, users_admin_link
):
    users = create_multiple_users()
    downloads_ids = [request_user_data_download(u).id for u in users]

    response = admin_client.post(
        users_admin_link,
        data={
            "action": "request_data_download",
            "selected_items": [u.id for u in users],
        },
    )

    assert not DataDownload.objects.exclude(id__in=downloads_ids).exists()


def test_multiple_users_can_be_deleted_with_mass_action(admin_client, users_admin_link):
    users = create_multiple_users()
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_accounts", "selected_items": [u.id for u in users]},
    )

    for user in users:
        with pytest.raises(User.DoesNotExist):
            user.refresh_from_db()


def test_mass_action_records_users_deletion_by_staff(admin_client, users_admin_link):
    users = create_multiple_users()
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_accounts", "selected_items": [u.id for u in users]},
    )

    deleted_count = DeletedUser.objects.filter(
        deleted_by=DeletedUser.DELETED_BY_STAFF
    ).count()
    assert deleted_count == len(users)


def test_delete_users_mass_action_fails_if_user_tries_to_delete_themselves(
    admin_client, users_admin_link, superuser
):
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_accounts", "selected_items": [superuser.id]},
    )
    assert_has_error_message(response)
    superuser.refresh_from_db()


def test_delete_users_mass_action_fails_if_user_tries_to_delete_staff_members(
    admin_client, users_admin_link
):
    users = create_multiple_users(is_staff=True)
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_accounts", "selected_items": [u.id for u in users]},
    )
    assert_has_error_message(response)

    for user in users:
        user.refresh_from_db()


def test_delete_users_mass_action_fails_if_user_tries_to_delete_superusers(
    admin_client, users_admin_link
):
    users = create_multiple_users(is_superuser=True)
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_accounts", "selected_items": [u.id for u in users]},
    )
    assert_has_error_message(response)

    for user in users:
        user.refresh_from_db()


@pytest.fixture
def mock_delete_user_with_content(mocker):
    delay = mocker.Mock()
    mocker.patch(
        "misago.users.admin.views.users.delete_user_with_content",
        mocker.Mock(delay=delay),
    )
    return delay


def test_multiple_users_can_be_deleted_together_with_content_by_mass_action(
    admin_client, users_admin_link, users_ids, mock_delete_user_with_content
):
    response = admin_client.post(
        users_admin_link, data={"action": "delete_all", "selected_items": users_ids}
    )

    calls = [call(u) for u in users_ids]
    mock_delete_user_with_content.assert_has_calls(calls, any_order=True)


def test_deleting_multiple_users_with_content_disables_their_accounts(
    admin_client, users_admin_link, mock_delete_user_with_content
):
    users = create_multiple_users()
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_all", "selected_items": [u.id for u in users]},
    )

    for user in users:
        user.refresh_from_db()
        assert not user.is_active


def test_delete_users_with_content_mass_action_fails_if_user_tries_to_delete_themselves(
    admin_client, users_admin_link, superuser, mock_delete_user_with_content
):
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_all", "selected_items": [superuser.id]},
    )

    assert_has_error_message(response)
    mock_delete_user_with_content.assert_not_called()

    superuser.refresh_from_db()
    assert superuser.is_active


def test_delete_users_with_content_mass_action_fails_if_user_tries_to_delete_staff(
    admin_client, users_admin_link, mock_delete_user_with_content
):
    users = create_multiple_users(is_staff=True)
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_all", "selected_items": [u.id for u in users]},
    )

    assert_has_error_message(response)
    mock_delete_user_with_content.assert_not_called()

    for user in users:
        user.refresh_from_db()
        assert user.is_active


def test_delete_users_with_content_mass_action_fails_if_user_tries_to_delete_superusers(
    admin_client, users_admin_link, mock_delete_user_with_content
):
    users = create_multiple_users(is_superuser=True)
    response = admin_client.post(
        users_admin_link,
        data={"action": "delete_all", "selected_items": [u.id for u in users]},
    )

    assert_has_error_message(response)
    mock_delete_user_with_content.assert_not_called()

    for user in users:
        user.refresh_from_db()
        assert user.is_active
