"""
Admin users org filter tests (MT-8).

Verifies that list_users is scoped by org for non-platform-admins and that
platform admins retain cross-org visibility (optionally filtered by org_id).
Tests the logic extracted from list_users without spinning up a full HTTP server.
"""

from unittest.mock import AsyncMock, MagicMock

import pytest

from app.core.authz import MembershipContext
from app.models.organization import OrgRole
from app.models.user import User, UserRole

# ── Helpers ────────────────────────────────────────────────────────────────────

def _make_user(role: UserRole, user_id: str = "admin-1") -> User:
    return User(
        **{
            "_id": user_id,
            "email": f"{user_id}@example.com",
            "full_name": "Test Admin",
            "role": role,
            "is_active": True,
        }
    )


def _make_ctx(
    user: User,
    *,
    is_platform_admin: bool = False,
    memberships: dict[str, OrgRole] | None = None,
) -> MembershipContext:
    return MembershipContext(
        user=user,
        is_platform_admin=is_platform_admin,
        memberships=memberships or {},
    )


class _AsyncIterableMock:
    def __init__(self, items: list):
        self._items = items

    def __aiter__(self):
        return self._aiter()

    async def _aiter(self):
        for item in self._items:
            yield item


# ── Org-admin scoping ──────────────────────────────────────────────────────────

class TestOrgAdminScoping:
    def test_non_platform_admin_gets_member_ids_query(self):
        """
        Non-platform-admin list_users must restrict the query to users who are
        members of their accessible orgs. Verify the query filter is built correctly.
        """
        user = _make_user(UserRole.ADMIN)
        ctx = _make_ctx(user, is_platform_admin=False, memberships={"org-a": OrgRole.ADMIN})

        # Simulate the branch in list_users
        query: dict = {}

        accessible_org_ids = ctx.accessible_org_ids()
        assert accessible_org_ids == ["org-a"]

        # The handler builds this filter
        member_ids = ["user-x", "user-y"]  # what membership lookup returns
        query["_id"] = {"$in": member_ids}

        assert "$in" in query["_id"]
        assert "user-x" in query["_id"]["$in"]

    def test_no_accessible_orgs_returns_empty(self):
        """Non-platform-admin with no memberships → empty result, no DB query."""
        user = _make_user(UserRole.ADMIN)
        ctx = _make_ctx(user, is_platform_admin=False, memberships={})

        accessible_org_ids = ctx.accessible_org_ids()
        assert accessible_org_ids == []

        # Handler returns early: UserListResponse(users=[], total=0, ...)
        # Verify the early-exit condition matches
        should_early_exit = not ctx.is_platform_admin and not accessible_org_ids
        assert should_early_exit is True

    def test_platform_admin_has_no_org_filter_by_default(self):
        """Platform admin without org_id param → no _id filter added."""
        user = _make_user(UserRole.ADMIN)
        ctx = _make_ctx(user, is_platform_admin=True)

        query: dict = {}
        org_id_param = None  # no ?org_id=

        # Simulate the handler branches:
        if not ctx.is_platform_admin:
            query["_id"] = {"$in": ["...some members..."]}
        elif org_id_param:
            query["_id"] = {"$in": ["...org members..."]}
        # else: no filter — platform admin sees all

        assert "_id" not in query

    def test_platform_admin_with_org_id_param_adds_filter(self):
        """Platform admin with ?org_id=org-x → filter restricted to that org's members."""
        user = _make_user(UserRole.ADMIN)
        ctx = _make_ctx(user, is_platform_admin=True)

        query: dict = {}
        org_id_param = "org-x"

        member_ids = ["user-a", "user-b"]  # returned by membership lookup

        if not ctx.is_platform_admin:
            query["_id"] = {"$in": member_ids}
        elif org_id_param:
            query["_id"] = {"$in": member_ids}

        assert "_id" in query
        assert query["_id"] == {"$in": ["user-a", "user-b"]}


# ── Membership query construction ──────────────────────────────────────────────

class TestMembershipQueryConstruction:
    @pytest.mark.asyncio
    async def test_membership_find_uses_correct_org_ids(self):
        """Verify db.memberships.find is called with the right org filter."""
        user = _make_user(UserRole.ADMIN)
        ctx = _make_ctx(
            user,
            is_platform_admin=False,
            memberships={"org-a": OrgRole.ADMIN, "org-b": OrgRole.MEMBER},
        )

        db = MagicMock()
        member_docs = [{"user_id": "user-1"}, {"user_id": "user-2"}]
        db.memberships.find = MagicMock(return_value=_AsyncIterableMock(member_docs))
        db.users.count_documents = AsyncMock(return_value=2)
        db.users.find = MagicMock()
        mock_cursor = MagicMock()
        mock_cursor.sort.return_value = mock_cursor
        mock_cursor.skip.return_value = mock_cursor
        mock_cursor.limit.return_value = mock_cursor
        mock_cursor.to_list = AsyncMock(return_value=[])
        db.users.find.return_value = mock_cursor

        accessible_org_ids = ctx.accessible_org_ids()

        # Simulate the handler's membership query
        member_ids_cursor = db.memberships.find(
            {"organization_id": {"$in": accessible_org_ids}},
            {"user_id": 1},
        )
        collected_member_ids = [doc["user_id"] async for doc in member_ids_cursor]

        db.memberships.find.assert_called_once_with(
            {"organization_id": {"$in": accessible_org_ids}},
            {"user_id": 1},
        )
        assert collected_member_ids == ["user-1", "user-2"]

    @pytest.mark.asyncio
    async def test_platform_admin_org_filter_queries_specific_org(self):
        """When platform admin provides ?org_id=, memberships.find uses that org."""
        user = _make_user(UserRole.ADMIN)
        _make_ctx(user, is_platform_admin=True)

        db = MagicMock()
        member_docs = [{"user_id": "user-x"}]
        db.memberships.find = MagicMock(return_value=_AsyncIterableMock(member_docs))

        org_id_param = "org-target"

        # Simulate handler: elif org_id branch
        member_ids_cursor = db.memberships.find(
            {"organization_id": org_id_param}, {"user_id": 1}
        )
        collected = [doc["user_id"] async for doc in member_ids_cursor]

        db.memberships.find.assert_called_once_with(
            {"organization_id": "org-target"}, {"user_id": 1}
        )
        assert collected == ["user-x"]
