"""Unit tests for UserService (U3 — user CRUD + multi-department assignment).

Covers:
- Happy path: create_user → list_users returns it → get_user → update_user →
  reset_password (verify old sessions revoked) → assign_department →
  list_user_departments → remove_department → delete_user (soft)
- Edge case: create duplicate username → ValueError
- Edge case: create duplicate email → ValueError
- Edge case: get/update/delete non-existent user → None/ValueError/False
- Edge case: assign duplicate department → ValueError
- Edge case: assign non-existent department → ValueError
- Edge case: list_users filtered by department_id
- Edge case: delete_user is soft (is_active=0, row still exists)
"""

from __future__ import annotations

import uuid
from collections.abc import Iterator
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import aiosqlite
import bcrypt
import pytest

from agentkit.server.admin.department_service import DepartmentService
from agentkit.server.admin.user_service import UserService
from agentkit.server.auth.models import init_auth_db
from agentkit.server.auth.session_service import (
    REVOKE_REASON_PASSWORD_CHANGED,
    SessionService,
    set_session_service,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
async def fresh_db(tmp_path: Path) -> Path:
    """A brand-new auth DB on a fresh path (no data)."""
    db_path = tmp_path / "auth.db"
    await init_auth_db(db_path)
    return db_path


@pytest.fixture
def service() -> UserService:
    return UserService()


@pytest.fixture
def department_service() -> DepartmentService:
    return DepartmentService()


@pytest.fixture
def session_service(fresh_db: Path) -> Iterator[SessionService]:
    """A SessionService backed by the same temp DB as fresh_db.

    Installed as the process-wide singleton so that
    ``UserService.reset_password`` can find it via
    ``get_session_service()``.
    """
    svc = SessionService(db_path=fresh_db)
    set_session_service(svc)
    yield svc
    # Restore the lazy default after the test.
    set_session_service(None)


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


async def _create_test_department(
    dept_service: DepartmentService, db_path: Path, name: str = "Engineering"
) -> dict[str, Any]:
    """Create a department for use in user-assignment tests."""
    return await dept_service.create_department(db_path, name)


async def _create_session_for_user(
    db_path: Path, user_id: str, session_id: str | None = None
) -> str:
    """Insert a minimal active auth_sessions row and return its id."""
    session_id = session_id or str(uuid.uuid4())
    now = _now_iso()
    expires = datetime.now(timezone.utc).isoformat()
    async with aiosqlite.connect(str(db_path)) as db:
        await db.execute(
            "INSERT INTO auth_sessions "
            "(id, user_id, refresh_token_hash, device_fingerprint, device_label, "
            " ip, user_agent, auth_provider, created_at, last_active_at, expires_at, "
            " revoked, revoked_reason, previous_session_id) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (
                session_id,
                user_id,
                f"hash-{session_id[:8]}",
                "fp-test",
                "Test device",
                "127.0.0.1",
                "test-agent",
                "local",
                now,
                now,
                expires,
                0,
                None,
                None,
            ),
        )
        await db.commit()
    return session_id


async def _count_active_sessions(db_path: Path, user_id: str) -> int:
    """Count active (non-revoked) sessions for a user."""
    async with aiosqlite.connect(str(db_path)) as db:
        cursor = await db.execute(
            "SELECT COUNT(*) FROM auth_sessions WHERE user_id = ? AND revoked = 0",
            (user_id,),
        )
        row = await cursor.fetchone()
    return int(row[0]) if row else 0


async def _fetch_user_row(db_path: Path, user_id: str) -> dict[str, Any] | None:
    """Fetch a raw user row (including is_active) directly from the DB."""
    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute("SELECT * FROM users WHERE id = ?", (user_id,))
        row = await cursor.fetchone()
    return dict(row) if row else None


# ---------------------------------------------------------------------------
# create_user happy path
# ---------------------------------------------------------------------------


class TestCreateUserHappyPath:
    async def test_create_returns_user_dict(self, service: UserService, fresh_db: Path):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        assert user["id"]
        assert user["username"] == "alice"
        assert user["email"] == "alice@example.com"
        assert user["role"] == "member"
        assert user["is_active"] is True
        assert user["is_terminal_authorized"] is False
        assert user["is_server_terminal_authorized"] is False
        assert user["created_by"] is None
        assert "created_at" in user
        assert "updated_at" in user
        assert "password_hash" not in user  # password_hash must not leak
        assert user["departments"] == []

    async def test_create_with_role_and_flags(self, service: UserService, fresh_db: Path):
        user = await service.create_user(
            fresh_db,
            "bob",
            "bob@example.com",
            "Secret123!",
            role="admin",
        )
        assert user["role"] == "admin"

    async def test_create_with_created_by(self, service: UserService, fresh_db: Path):
        admin_id = str(uuid.uuid4())
        user = await service.create_user(
            fresh_db,
            "carol",
            "carol@example.com",
            "Secret123!",
            created_by=admin_id,
        )
        assert user["created_by"] == admin_id

    async def test_create_with_department_ids_assigns_departments(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        hr = await _create_test_department(department_service, fresh_db, "HR")
        user = await service.create_user(
            fresh_db,
            "dave",
            "dave@example.com",
            "Secret123!",
            department_ids=[eng["id"], hr["id"]],
        )
        dept_ids = {d["id"] for d in user["departments"]}
        assert dept_ids == {eng["id"], hr["id"]}

    async def test_password_is_bcrypt_hashed(
        self, service: UserService, fresh_db: Path
    ):
        """The stored password_hash must be a bcrypt hash, not plaintext."""
        user = await service.create_user(
            fresh_db, "eve", "eve@example.com", "MyPassword123!"
        )
        row = await _fetch_user_row(fresh_db, user["id"])
        assert row is not None
        stored_hash = row["password_hash"]
        # bcrypt hashes start with $2b$ (or $2a$/$2y$).
        assert stored_hash.startswith("$2")
        # And the hash must verify against the original password.
        assert bcrypt.checkpw(b"MyPassword123!", stored_hash.encode())


# ---------------------------------------------------------------------------
# create_user edge cases
# ---------------------------------------------------------------------------


class TestCreateUserEdgeCases:
    async def test_duplicate_username_raises_value_error(
        self, service: UserService, fresh_db: Path
    ):
        await service.create_user(fresh_db, "alice", "alice@example.com", "Secret123!")
        with pytest.raises(ValueError, match="username"):
            await service.create_user(
                fresh_db, "alice", "other@example.com", "Secret123!"
            )

    async def test_duplicate_email_raises_value_error(
        self, service: UserService, fresh_db: Path
    ):
        await service.create_user(fresh_db, "alice", "alice@example.com", "Secret123!")
        with pytest.raises(ValueError, match="email"):
            await service.create_user(
                fresh_db, "alice2", "alice@example.com", "Secret123!"
            )

    async def test_create_with_nonexistent_department_id_raises(
        self,
        service: UserService,
        fresh_db: Path,
    ):
        bogus_dept_id = str(uuid.uuid4())
        with pytest.raises(ValueError, match="Department.*not found"):
            await service.create_user(
                fresh_db,
                "frank",
                "frank@example.com",
                "Secret123!",
                department_ids=[bogus_dept_id],
            )

    async def test_create_with_duplicate_department_id_raises(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        with pytest.raises(ValueError, match="already assigned"):
            await service.create_user(
                fresh_db,
                "gina",
                "gina@example.com",
                "Secret123!",
                department_ids=[eng["id"], eng["id"]],
            )


# ---------------------------------------------------------------------------
# list_users
# ---------------------------------------------------------------------------


class TestListUsers:
    async def test_list_returns_all_users(self, service: UserService, fresh_db: Path):
        await service.create_user(fresh_db, "alice", "alice@example.com", "Secret123!")
        await service.create_user(fresh_db, "bob", "bob@example.com", "Secret123!")
        users = await service.list_users(fresh_db)
        assert len(users) == 2
        names = {u["username"] for u in users}
        assert names == {"alice", "bob"}

    async def test_list_excludes_inactive_when_asked(
        self, service: UserService, fresh_db: Path
    ):
        alice = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.create_user(fresh_db, "bob", "bob@example.com", "Secret123!")
        # Soft-delete alice.
        await service.delete_user(fresh_db, alice["id"])
        active_only = await service.list_users(fresh_db, include_inactive=False)
        assert len(active_only) == 1
        assert active_only[0]["username"] == "bob"
        all_users = await service.list_users(fresh_db, include_inactive=True)
        assert len(all_users) == 2

    async def test_list_filtered_by_department(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        hr = await _create_test_department(department_service, fresh_db, "HR")

        alice = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        bob = await service.create_user(
            fresh_db, "bob", "bob@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, alice["id"], eng["id"])
        await service.assign_department(fresh_db, bob["id"], hr["id"])

        eng_users = await service.list_users(fresh_db, department_id=eng["id"])
        assert len(eng_users) == 1
        assert eng_users[0]["username"] == "alice"

        hr_users = await service.list_users(fresh_db, department_id=hr["id"])
        assert len(hr_users) == 1
        assert hr_users[0]["username"] == "bob"

    async def test_list_includes_departments_for_each_user(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        alice = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, alice["id"], eng["id"])
        users = await service.list_users(fresh_db)
        assert len(users) == 1
        assert len(users[0]["departments"]) == 1
        assert users[0]["departments"][0]["id"] == eng["id"]
        assert users[0]["departments"][0]["name"] == "Engineering"


# ---------------------------------------------------------------------------
# get_user
# ---------------------------------------------------------------------------


class TestGetUser:
    async def test_get_returns_user_with_departments(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, user["id"], eng["id"])
        fetched = await service.get_user(fresh_db, user["id"])
        assert fetched is not None
        assert fetched["username"] == "alice"
        assert len(fetched["departments"]) == 1
        assert fetched["departments"][0]["name"] == "Engineering"

    async def test_get_returns_none_for_unknown_id(
        self, service: UserService, fresh_db: Path
    ):
        result = await service.get_user(fresh_db, str(uuid.uuid4()))
        assert result is None


# ---------------------------------------------------------------------------
# update_user
# ---------------------------------------------------------------------------


class TestUpdateUser:
    async def test_update_role(self, service: UserService, fresh_db: Path):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        updated = await service.update_user(fresh_db, user["id"], role="admin")
        assert updated["role"] == "admin"

    async def test_update_is_active(self, service: UserService, fresh_db: Path):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        updated = await service.update_user(fresh_db, user["id"], is_active=False)
        assert updated["is_active"] is False

    async def test_update_terminal_authorized_flags(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        updated = await service.update_user(
            fresh_db,
            user["id"],
            is_terminal_authorized=True,
            is_server_terminal_authorized=True,
        )
        assert updated["is_terminal_authorized"] is True
        assert updated["is_server_terminal_authorized"] is True

    async def test_update_partial_only_changes_provided_fields(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        # Only update role; other fields should be preserved.
        updated = await service.update_user(fresh_db, user["id"], role="admin")
        assert updated["role"] == "admin"
        assert updated["is_active"] is True
        assert updated["is_terminal_authorized"] is False

    async def test_update_nonexistent_raises_value_error(
        self, service: UserService, fresh_db: Path
    ):
        with pytest.raises(ValueError, match="not found"):
            await service.update_user(fresh_db, str(uuid.uuid4()), role="admin")

    async def test_update_no_fields_provided_is_noop(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        updated = await service.update_user(fresh_db, user["id"])
        assert updated["username"] == "alice"


# ---------------------------------------------------------------------------
# delete_user (soft delete)
# ---------------------------------------------------------------------------


class TestDeleteUser:
    async def test_delete_returns_true_for_active_user(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        deleted = await service.delete_user(fresh_db, user["id"])
        assert deleted is True

    async def test_delete_is_soft(self, service: UserService, fresh_db: Path):
        """delete_user must set is_active=0 but NOT remove the row."""
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.delete_user(fresh_db, user["id"])
        # Row must still exist.
        row = await _fetch_user_row(fresh_db, user["id"])
        assert row is not None
        # But is_active must be 0.
        assert bool(row["is_active"]) is False

    async def test_delete_returns_false_for_unknown_id(
        self, service: UserService, fresh_db: Path
    ):
        deleted = await service.delete_user(fresh_db, str(uuid.uuid4()))
        assert deleted is False

    async def test_delete_returns_false_for_already_inactive(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        first = await service.delete_user(fresh_db, user["id"])
        assert first is True
        # Second delete on the same (now-inactive) user must return False.
        second = await service.delete_user(fresh_db, user["id"])
        assert second is False


# ---------------------------------------------------------------------------
# reset_password
# ---------------------------------------------------------------------------


class TestResetPassword:
    async def test_reset_password_returns_true(
        self,
        service: UserService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "OldPassword123!"
        )
        reset = await service.reset_password(fresh_db, user["id"], "NewPassword456!")
        assert reset is True

    async def test_reset_password_updates_hash(
        self,
        service: UserService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "OldPassword123!"
        )
        old_row = await _fetch_user_row(fresh_db, user["id"])
        await service.reset_password(fresh_db, user["id"], "NewPassword456!")
        new_row = await _fetch_user_row(fresh_db, user["id"])
        assert new_row["password_hash"] != old_row["password_hash"]
        # New hash must verify against the new password.
        assert bcrypt.checkpw(b"NewPassword456!", new_row["password_hash"].encode())

    async def test_reset_password_revokes_all_sessions(
        self,
        service: UserService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "OldPassword123!"
        )
        # Create two active sessions for the user.
        await _create_session_for_user(fresh_db, user["id"])
        await _create_session_for_user(fresh_db, user["id"])
        assert await _count_active_sessions(fresh_db, user["id"]) == 2

        await service.reset_password(fresh_db, user["id"], "NewPassword456!")
        # All sessions must be revoked.
        assert await _count_active_sessions(fresh_db, user["id"]) == 0

    async def test_reset_password_records_revoked_reason(
        self,
        service: UserService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "OldPassword123!"
        )
        session_id = await _create_session_for_user(fresh_db, user["id"])
        await service.reset_password(fresh_db, user["id"], "NewPassword456!")

        async with aiosqlite.connect(str(fresh_db)) as db:
            db.row_factory = aiosqlite.Row
            cursor = await db.execute(
                "SELECT revoked, revoked_reason FROM auth_sessions WHERE id = ?",
                (session_id,),
            )
            row = await cursor.fetchone()
        assert row is not None
        assert bool(row["revoked"]) is True
        assert row["revoked_reason"] == REVOKE_REASON_PASSWORD_CHANGED

    async def test_reset_password_returns_false_for_unknown_user(
        self,
        service: UserService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        reset = await service.reset_password(
            fresh_db, str(uuid.uuid4()), "NewPassword456!"
        )
        assert reset is False


# ---------------------------------------------------------------------------
# Department assignment
# ---------------------------------------------------------------------------


class TestAssignDepartment:
    async def test_assign_returns_assignment_dict(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        result = await service.assign_department(fresh_db, user["id"], eng["id"])
        assert result["user_id"] == user["id"]
        assert result["department_id"] == eng["id"]
        assert "created_at" in result

    async def test_assign_duplicate_raises_value_error(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, user["id"], eng["id"])
        with pytest.raises(ValueError, match="already assigned"):
            await service.assign_department(fresh_db, user["id"], eng["id"])

    async def test_assign_nonexistent_department_raises_value_error(
        self,
        service: UserService,
        fresh_db: Path,
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        with pytest.raises(ValueError, match="Department.*not found"):
            await service.assign_department(fresh_db, user["id"], str(uuid.uuid4()))


class TestRemoveDepartment:
    async def test_remove_returns_true(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, user["id"], eng["id"])
        removed = await service.remove_department(fresh_db, user["id"], eng["id"])
        assert removed is True
        # Confirm the assignment is gone.
        depts = await service.list_user_departments(fresh_db, user["id"])
        assert depts == []

    async def test_remove_returns_false_for_nonexistent_assignment(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        removed = await service.remove_department(fresh_db, user["id"], eng["id"])
        assert removed is False


class TestListUserDepartments:
    async def test_list_returns_departments_with_active_flag(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        hr = await _create_test_department(department_service, fresh_db, "HR")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, user["id"], eng["id"])
        await service.assign_department(fresh_db, user["id"], hr["id"])

        depts = await service.list_user_departments(fresh_db, user["id"])
        assert len(depts) == 2
        # Ordered by name.
        assert depts[0]["name"] == "Engineering"
        assert depts[1]["name"] == "HR"
        assert all("is_active" in d for d in depts)
        assert all(d["is_active"] is True for d in depts)

    async def test_list_returns_empty_for_user_with_no_departments(
        self, service: UserService, fresh_db: Path
    ):
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        depts = await service.list_user_departments(fresh_db, user["id"])
        assert depts == []

    async def test_list_reflects_department_active_state(
        self,
        service: UserService,
        department_service: DepartmentService,
        fresh_db: Path,
    ):
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db, "alice", "alice@example.com", "Secret123!"
        )
        await service.assign_department(fresh_db, user["id"], eng["id"])
        # Disable the department.
        await department_service.set_department_active(fresh_db, eng["id"], False)
        depts = await service.list_user_departments(fresh_db, user["id"])
        assert len(depts) == 1
        assert depts[0]["is_active"] is False


# ---------------------------------------------------------------------------
# End-to-end happy path
# ---------------------------------------------------------------------------


class TestEndToEndHappyPath:
    async def test_full_user_lifecycle(
        self,
        service: UserService,
        department_service: DepartmentService,
        session_service: SessionService,
        fresh_db: Path,
    ):
        """Exercise the full user lifecycle in order:
        create → list → get → update → reset_password → assign_department →
        list_user_departments → remove_department → delete (soft).
        """
        # 1. Create a department and a user assigned to it.
        eng = await _create_test_department(department_service, fresh_db, "Engineering")
        user = await service.create_user(
            fresh_db,
            "alice",
            "alice@example.com",
            "InitialPassword123!",
            department_ids=[eng["id"]],
        )
        assert user["username"] == "alice"
        assert len(user["departments"]) == 1

        # 2. list_users returns the new user.
        users = await service.list_users(fresh_db)
        assert len(users) == 1
        assert users[0]["id"] == user["id"]

        # 3. get_user returns the same user with departments.
        fetched = await service.get_user(fresh_db, user["id"])
        assert fetched is not None
        assert fetched["username"] == "alice"
        assert len(fetched["departments"]) == 1

        # 4. update_user changes the role.
        updated = await service.update_user(fresh_db, user["id"], role="admin")
        assert updated["role"] == "admin"

        # 5. reset_password updates the hash and revokes sessions.
        await _create_session_for_user(fresh_db, user["id"])
        assert await _count_active_sessions(fresh_db, user["id"]) == 1
        reset = await service.reset_password(fresh_db, user["id"], "NewPassword456!")
        assert reset is True
        assert await _count_active_sessions(fresh_db, user["id"]) == 0

        # 6. assign_department adds a second department.
        hr = await _create_test_department(department_service, fresh_db, "HR")
        await service.assign_department(fresh_db, user["id"], hr["id"])

        # 7. list_user_departments returns both.
        depts = await service.list_user_departments(fresh_db, user["id"])
        assert len(depts) == 2

        # 8. remove_department removes one.
        removed = await service.remove_department(fresh_db, user["id"], eng["id"])
        assert removed is True
        depts = await service.list_user_departments(fresh_db, user["id"])
        assert len(depts) == 1
        assert depts[0]["name"] == "HR"

        # 9. delete_user is a soft delete.
        deleted = await service.delete_user(fresh_db, user["id"])
        assert deleted is True
        row = await _fetch_user_row(fresh_db, user["id"])
        assert row is not None  # row still exists
        assert bool(row["is_active"]) is False  # but inactive


# ---------------------------------------------------------------------------
# Singleton helpers
# ---------------------------------------------------------------------------


class TestSingletonHelpers:
    def test_get_user_service_returns_singleton(self):
        from agentkit.server.admin.user_service import (
            get_user_service,
            set_user_service,
        )

        # Save the original singleton so we don't disturb other tests.
        original = get_user_service()
        try:
            custom = UserService()
            set_user_service(custom)
            assert get_user_service() is custom
            # Clearing falls back to a new lazy instance.
            set_user_service(None)
            new_one = get_user_service()
            assert new_one is not custom
        finally:
            set_user_service(original)
