"""RBAC: seed command, scope helper, object-level permission, role enforcement."""
import pytest
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient

from apps.core.models import Permission, Role, UserRole
from apps.core.permissions import IsOwnerOrFullScope, user_has_full_scope

User = get_user_model()


def _user(email):
    return User.objects.create_user(email=email, password="pass123")


def _assign(user, *codenames):
    """Give `user` a role carrying exactly the given `domain.action` perms."""
    role = Role.objects.create(name=f"r-{user.email}", slug=f"r-{user.pk}")
    for c in codenames:
        d, a = c.split(".", 1)
        perm, _ = Permission.objects.get_or_create(domain=d, action=a, defaults={"label": c})
        role.permissions.add(perm)
    UserRole.objects.create(user=user, role=role)
    return user


# ---- seed_rbac command ----

@pytest.mark.django_db
def test_seed_rbac_idempotent():
    from django.core.management import call_command
    call_command("seed_rbac")
    perms_after_first = Permission.objects.count()
    roles = {r.slug: r.permissions.count() for r in Role.objects.filter(
        slug__in=["manager", "staff", "visitor"])}
    assert set(roles) == {"manager", "staff", "visitor"}
    assert roles["manager"] > roles["staff"] > roles["visitor"]
    # Re-run: no new permissions, role perm counts unchanged.
    call_command("seed_rbac")
    assert Permission.objects.count() == perms_after_first
    assert {r.slug: r.permissions.count() for r in Role.objects.filter(
        slug__in=["manager", "staff", "visitor"])} == roles


# ---- user_has_full_scope ----

@pytest.mark.django_db
def test_full_scope_requires_delete_perm():
    manager = _assign(_user("m@x.com"), "tasks.delete")
    staff = _assign(_user("s@x.com"), "tasks.view", "tasks.create")
    assert user_has_full_scope(manager, "tasks") is True
    assert user_has_full_scope(staff, "tasks") is False


@pytest.mark.django_db
def test_full_scope_superuser_always():
    su = User.objects.create_superuser(email="su@x.com", password="p")
    assert user_has_full_scope(su, "anything") is True


@pytest.mark.django_db
def test_full_scope_empty_domain_false():
    u = _assign(_user("u@x.com"), "tasks.delete")
    assert user_has_full_scope(u, "") is False


# ---- IsOwnerOrFullScope ----

class _Obj:
    def __init__(self, **kw):
        self.__dict__.update(kw)


class _View:
    rbac_domain = "tasks"


class _Req:
    def __init__(self, user, method="PATCH"):
        self.user = user
        self.method = method


@pytest.mark.django_db
def test_object_perm_owner_can_write():
    owner = _assign(_user("o@x.com"), "tasks.view")
    obj = _Obj(created_by_id=owner.id)
    assert IsOwnerOrFullScope().has_object_permission(_Req(owner), _View(), obj) is True


@pytest.mark.django_db
def test_object_perm_non_owner_blocked():
    owner = _user("o2@x.com")
    other = _assign(_user("x2@x.com"), "tasks.view")
    obj = _Obj(created_by_id=owner.id)
    assert IsOwnerOrFullScope().has_object_permission(_Req(other), _View(), obj) is False


@pytest.mark.django_db
def test_object_perm_full_scope_writes_any():
    mgr = _assign(_user("m2@x.com"), "tasks.delete")
    obj = _Obj(created_by_id=_user("z@x.com").id)
    assert IsOwnerOrFullScope().has_object_permission(_Req(mgr), _View(), obj) is True


@pytest.mark.django_db
def test_object_perm_reads_open():
    other = _assign(_user("r@x.com"), "tasks.view")
    obj = _Obj(created_by_id=_user("zz@x.com").id)
    assert IsOwnerOrFullScope().has_object_permission(_Req(other, "GET"), _View(), obj) is True


# ---- end-to-end: pdf_tools no longer publicly executable ----

@pytest.mark.django_db
def test_pdf_tools_not_public():
    """Unauthenticated POST must NOT execute the PDF op. Accepts 401/403 (now
    auth-gated) or 404 (route not currently wired into config.urls) — either way
    it is not a public 200/400 like before the permission_classes change."""
    client = APIClient()
    resp = client.post("/api/pdf-tools/compress/")
    assert resp.status_code in (401, 403, 404)


# ---- end-to-end: scoped user only sees/edits own records ----

@pytest.mark.django_db
def test_staff_task_list_scoped_to_own():
    from apps.tasks.models import Task
    owner = _assign(_user("ta@x.com"), "tasks.view", "tasks.create")
    other = _assign(_user("tb@x.com"), "tasks.view", "tasks.create")
    Task.objects.create(title="mine", created_by=owner)
    Task.objects.create(title="theirs", created_by=other)
    client = APIClient()
    client.force_authenticate(user=owner)
    resp = client.get("/api/tasks/")
    assert resp.status_code == 200
    body = resp.json()
    rows = body["results"] if isinstance(body, dict) else body
    titles = {t["title"] for t in rows}
    assert "mine" in titles and "theirs" not in titles


@pytest.mark.django_db
def test_manager_task_list_sees_all():
    from apps.tasks.models import Task
    mgr = _assign(_user("tm@x.com"), "tasks.view", "tasks.delete")
    other = _user("tc@x.com")
    Task.objects.create(title="theirs", created_by=other)
    client = APIClient()
    client.force_authenticate(user=mgr)
    resp = client.get("/api/tasks/")
    body = resp.json()
    rows = body["results"] if isinstance(body, dict) else body
    titles = {t["title"] for t in rows}
    assert "theirs" in titles
