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

import hashlib
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


def test_expired_invitation_has_its_own_status_and_filter(client):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    pending = create_account(email='pending@example.test', is_active=False)
    expired = create_account(email='expired@example.test', is_active=False)
    Invitation.objects.create(
        account=pending,
        invited_by=admin,
        token_hash='a' * 64,
        expires_at=timezone.now() + timedelta(days=1),
    )
    Invitation.objects.create(
        account=expired,
        invited_by=admin,
        token_hash='b' * 64,
        expires_at=timezone.now() - timedelta(seconds=1),
    )
    client.force_login(admin)

    statuses = {row['email']: row['status'] for row in page(client)['rows']}
    assert statuses[pending.email] == 'INVITED'
    assert statuses[expired.email] == 'EXPIRED'
    assert [row['email'] for row in page(client, '?status=expired')['rows']] == [expired.email]


@pytest.mark.parametrize('expired', [False, True])
def test_admin_can_delete_pending_invitation_and_reinvite_email(client, expired):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    client.force_login(admin)
    account = create_account(email='pending@example.test', is_active=False)
    token = 'unused-invitation-token'
    Invitation.objects.create(
        account=account,
        invited_by=admin,
        token_hash=hashlib.sha256(token.encode()).hexdigest(),
        expires_at=timezone.now() + timedelta(days=-1 if expired else 1),
    )

    response = client.post(f'/users/{account.id}/invitation/delete')

    assert response.status_code == 302
    assert not get_user_model().objects.filter(pk=account.pk).exists()
    assert not Invitation.objects.filter(account_id=account.pk).exists()
    assert Client().post(f'/accept-invitation/{token}').status_code == 410

    with override_settings(
        MAILERS={'default': {'BACKEND': 'django.core.mail.backends.locmem.EmailBackend'}},
        DEFAULT_FROM_EMAIL='Tools <no-reply@example.test>',
    ):
        reinvite = client.post(
            '/users/invite',
            data=json.dumps({'email': account.email, 'role': AccountRole.VIEWER}),
            content_type='application/json',
        )
    assert reinvite.status_code == 201


def test_accepted_invitation_cannot_be_deleted(client):
    admin = create_account(role=AccountRole.ADMIN, is_staff=True)
    account = create_account(email='accepted@example.test')
    Invitation.objects.create(
        account=account,
        invited_by=admin,
        token_hash='c' * 64,
        expires_at=timezone.now() + timedelta(days=1),
        accepted_at=timezone.now(),
    )
    client.force_login(admin)

    assert client.post(f'/users/{account.id}/invitation/delete').status_code == 404
    assert get_user_model().objects.filter(pk=account.pk).exists()


def test_viewer_cannot_delete_invitation(client):
    viewer = create_account()
    account = create_account(email='pending@example.test', is_active=False)
    Invitation.objects.create(
        account=account,
        token_hash='d' * 64,
        expires_at=timezone.now() + timedelta(days=1),
    )
    client.force_login(viewer)

    assert client.post(f'/users/{account.id}/invitation/delete').status_code == 403
    assert get_user_model().objects.filter(pk=account.pk).exists()
