"""Performance regression tests: RBAC permission caching + admin users endpoint."""

import pytest
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.db import connection
from django.test.utils import CaptureQueriesContext
from rest_framework.test import APIClient

from apps.core.models import Permission, Role, UserRole

User = get_user_model()


@pytest.fixture(autouse=True)
def clear_cache():
    # RBAC perms now live in the Django cache; isolate tests from each other.
    cache.clear()
    yield
    cache.clear()


@pytest.fixture
def admin(db):
    return User.objects.create_superuser(email="admin@test.local", password="x")


@pytest.fixture
def member(db):
    user = User.objects.create_user(email="member@test.local", password="x")
    role = Role.objects.create(name="HR Staff", slug="hr-staff")
    # Permissions are seeded by a data migration — reuse if present.
    perm, _ = Permission.objects.get_or_create(
        domain="hr", action="view", defaults={"label": "View HR"})
    role.permissions.add(perm)
    UserRole.objects.create(user=user, role=role)
    return user


@pytest.mark.django_db
class TestRolePermissionCaching:
    def test_codenames_resolved_from_roles(self, member):
        assert member.role_permission_codenames == {"hr.view"}

    def test_superuser_short_circuits(self, admin):
        with CaptureQueriesContext(connection) as ctx:
            assert admin.role_permission_codenames == {"*"}
        assert len(ctx.captured_queries) == 0

    def test_repeated_access_hits_db_once(self, member):
        member.role_permission_codenames  # warm the cached_property
        with CaptureQueriesContext(connection) as ctx:
            member.role_permission_codenames
            member.has_menu_perm("hr.view")
            member.has_menu_perm("projects.view")
        assert len(ctx.captured_queries) == 0

    def test_has_menu_perm(self, member):
        assert member.has_menu_perm("hr.view") is True
        assert member.has_menu_perm("hr.create") is False
        assert member.has_menu_perm("projects.view") is False

    def test_domain_wildcard(self, db):
        user = User.objects.create_user(email="wild@test.local", password="x")
        role = Role.objects.create(name="HR Admin", slug="hr-admin")
        perm, _ = Permission.objects.get_or_create(
            domain="hr", action="*", defaults={"label": "All HR"})
        role.permissions.add(perm)
        UserRole.objects.create(user=user, role=role)
        assert user.has_menu_perm("hr.anything") is True

    def test_cross_request_cache(self, member):
        member.role_permission_codenames  # warms the Django cache
        fresh = User.objects.get(pk=member.pk)  # new instance = new request
        with CaptureQueriesContext(connection) as ctx:
            assert fresh.role_permission_codenames == {"hr.view"}
        assert len(ctx.captured_queries) == 0

    def test_assign_roles_invalidates_cache(self, admin, member):
        member.role_permission_codenames  # warm
        client = APIClient()
        client.force_authenticate(admin)
        resp = client.post(f"/api/rbac/users/{member.pk}/roles/",
                           {"role_ids": []}, format="json")
        assert resp.status_code == 200
        fresh = User.objects.get(pk=member.pk)
        assert fresh.role_permission_codenames == set()

    def test_set_role_permissions_invalidates_cache(self, admin, member):
        member.role_permission_codenames  # warm
        role = Role.objects.get(slug="hr-staff")
        client = APIClient()
        client.force_authenticate(admin)
        resp = client.post(f"/api/rbac/roles/{role.pk}/permissions/",
                           {"permission_ids": []}, format="json")
        assert resp.status_code == 200
        fresh = User.objects.get(pk=member.pk)
        assert fresh.role_permission_codenames == set()


@pytest.mark.django_db
class TestAdminUsersEndpoint:
    def test_roles_shape_and_constant_queries(self, admin, member):
        client = APIClient()
        client.force_authenticate(admin)

        resp = client.get("/api/rbac/users/")
        assert resp.status_code == 200
        body = resp.json()
        rows = body["results"] if isinstance(body, dict) and "results" in body else body
        by_email = {r["email"]: r for r in rows}
        roles = by_email["member@test.local"]["roles"]
        assert roles == [{"id": roles[0]["id"], "slug": "hr-staff", "name": "HR Staff"}]
        assert by_email["admin@test.local"]["roles"] == []

        # Query count must not grow with user count (prefetched roles).
        with CaptureQueriesContext(connection) as small:
            client.get("/api/rbac/users/")
        for i in range(5):
            u = User.objects.create_user(email=f"bulk{i}@test.local", password="x")
            UserRole.objects.create(user=u, role=Role.objects.get(slug="hr-staff"))
        with CaptureQueriesContext(connection) as big:
            client.get("/api/rbac/users/")
        assert len(big.captured_queries) == len(small.captured_queries)
