"""Gestión de usuarios: visibilidad, roles y desactivación efectiva."""

import json
from datetime import timedelta
from urllib.parse import urlsplit

import pytest
from django.contrib.auth import get_user_model
from django.core import mail
from django.core.management import call_command
from django.core.management.base import CommandError
from django.db import IntegrityError, transaction
from django.test import Client, override_settings
from django.utils import timezone

from apps.accounts.models import AccountRole, Invitation, Session
from tests.factories import create_account

pytestmark = pytest.mark.django_db


def page(client, query=''):
    response = client.get(f'/users{query}', HTTP_X_INERTIA='true')
    assert response.status_code == 200
    return response.json()['props']['users']


def test_only_administrative_roles_can_open_users(client):
    viewer = create_account()
    client.force_login(viewer)
    assert client.get('/users').status_code == 403

    viewer.role = AccountRole.ADMIN
    viewer.save(update_fields=['role'])
    assert client.get('/users').status_code == 200


def test_super_admin_is_never_visible(client):
    super_admin = create_account(
        email='owner@example.test', role=AccountRole.SUPER_ADMIN, is_staff=True
    )
    admin = create_account(email='admin@example.test', role=AccountRole.ADMIN, is_staff=True)
    viewer = create_account(email='viewer@example.test')
    client.force_login(admin)

    rows = page(client)['rows']

    assert {row['email'] for row in rows} == {admin.email, viewer.email}
    assert super_admin.email not in {row['email'] for row in rows}


def test_database_allows_only_one_super_admin():
    create_account(email='first@example.test', role=AccountRole.SUPER_ADMIN)

    with pytest.raises(IntegrityError), transaction.atomic():
        create_account(email='second@example.test', role=AccountRole.SUPER_ADMIN)


def test_set_super_admin_promotes_an_existing_account():
    account = create_account(is_active=False)

    call_command('set_super_admin', account.email)

    account.refresh_from_db()
    assert account.role == AccountRole.SUPER_ADMIN
    assert account.is_active is True
    assert account.is_staff is True
    assert account.is_superuser is True


def test_set_super_admin_never_replaces_an_existing_one():
    create_account(email='owner@example.test', role=AccountRole.SUPER_ADMIN)
    account = create_account(email='other@example.test')

    with pytest.raises(CommandError, match='already active'):
        call_command('set_super_admin', account.email)


def test_admin_can_change_only_admin_and_viewer_roles(client):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    viewer = create_account(email='viewer@example.test')
    client.force_login(admin)

    response = client.post(f'/users/{viewer.id}/role', {'role': AccountRole.ADMIN})
    assert response.status_code == 302
    viewer.refresh_from_db()
    assert viewer.role == AccountRole.ADMIN
    assert viewer.is_staff is True

    response = client.post(f'/users/{viewer.id}/role', {'role': AccountRole.SUPER_ADMIN})
    assert response.status_code == 403
    viewer.refresh_from_db()
    assert viewer.role == AccountRole.ADMIN


def test_deactivation_closes_every_session_and_hides_account_at_login(client):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    viewer = create_account(email='viewer@example.test')
    browser = Client()
    login = browser.post(
        '/login', {'email': viewer.email, 'password': 'contraseña-de-prueba'}
    )
    assert login.status_code == 302
    assert Session.objects.filter(account=viewer, revoked_at=None).exists()
    client.force_login(admin)

    response = client.post(f'/users/{viewer.id}/deactivate')

    assert response.status_code == 302
    viewer.refresh_from_db()
    assert viewer.is_active is False
    assert not Session.objects.filter(account=viewer, revoked_at=None).exists()
    assert browser.get('/').status_code == 302
    login = Client().post('/login', {'email': viewer.email, 'password': 'contraseña-de-prueba'})
    assert login.status_code == 302
    assert login.url == '/login'


@override_settings(
    MAILERS={'default': {'BACKEND': 'django.core.mail.backends.locmem.EmailBackend'}},
    DEFAULT_FROM_EMAIL='Tools <no-reply@example.test>',
)
def test_admin_invites_by_email_and_invited_user_activates_the_account(client):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    client.force_login(admin)

    response = client.post(
        '/users/invite',
        data=json.dumps({'email': 'invited@example.test', 'role': AccountRole.VIEWER}),
        content_type='application/json',
    )

    assert response.status_code == 201
    body = response.json()
    account = get_user_model().objects.get(email='invited@example.test')
    invitation = Invitation.objects.get(account=account)
    assert account.is_active is False
    assert invitation.expires_at == pytest.approx(
        timezone.now() + timedelta(days=3), abs=timedelta(seconds=5)
    )
    assert len(mail.outbox) == 1
    assert body['invite_url'] in mail.outbox[0].body

    path = urlsplit(body['invite_url']).path
    accepted = Client().post(
        path,
        data=json.dumps(
            {'password': 'safe-password-123', 'password_confirmation': 'safe-password-123'}
        ),
        content_type='application/json',
    )
    assert accepted.status_code == 200
    account.refresh_from_db()
    invitation.refresh_from_db()
    assert account.is_active is True
    assert invitation.accepted_at is not None


def test_viewer_cannot_invite_users(client):
    viewer = create_account()
    client.force_login(viewer)
    response = client.post(
        '/users/invite',
        data=json.dumps({'email': 'invited@example.test', 'role': AccountRole.VIEWER}),
        content_type='application/json',
    )
    assert response.status_code == 403
