"""Tests for the ``backfill_library_memberships`` management command.

The command queries ``library.models.Library.nodes`` (Neo4j), which we
don't want to exercise in unit tests. We substitute a lightweight fake
library iterable onto ``Library.nodes.filter`` via ``unittest.mock``.
"""

from __future__ import annotations

from io import StringIO
from unittest import mock

from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase

from mcp_server.models import LibraryMembership

User = get_user_model()


class _FakeLibrary:
    def __init__(self, uid: str, workspace_id=None):
        self.uid = uid
        self.workspace_id = workspace_id


class _FakeNodes:
    """Stand-in for the neomodel ``Library.nodes`` node set."""

    def __init__(self, all_libs):
        self._all = list(all_libs)

    def filter(self, **kwargs):
        out = list(self._all)
        if "workspace_id__isnull" in kwargs:
            want = kwargs["workspace_id__isnull"]
            out = [l for l in out if (l.workspace_id is None) == want]
        return out


def _run(*cli_args):
    """Invoke the command with stdout/stderr buffered. Returns stdout."""
    buf = StringIO()
    call_command(
        "backfill_library_memberships", *cli_args, stdout=buf, stderr=StringIO()
    )
    return buf.getvalue()


class BackfillCommandTest(TestCase):
    def setUp(self):
        self.superuser = User.objects.create_superuser(
            username="admin", password="pw", email="a@b.c"
        )
        self.patcher = mock.patch(
            "library.models.Library.nodes",
            new=_FakeNodes(
                [
                    _FakeLibrary("lib-a"),              # global
                    _FakeLibrary("lib-b"),              # global
                    _FakeLibrary("lib-ws", "ws-1"),     # workspace-scoped
                ]
            ),
        )
        self.patcher.start()
        self.addCleanup(self.patcher.stop)

    def test_assigns_owner_to_all_global_libraries(self):
        _run()
        owners = LibraryMembership.objects.filter(user=self.superuser)
        self.assertEqual(
            sorted(owners.values_list("library_uid", flat=True)),
            ["lib-a", "lib-b"],
        )
        self.assertTrue(
            all(
                m.role == LibraryMembership.Role.OWNER
                for m in owners
            )
        )

    def test_skips_workspace_scoped_libraries(self):
        _run()
        self.assertFalse(
            LibraryMembership.objects.filter(library_uid="lib-ws").exists()
        )

    def test_idempotent(self):
        _run()
        count_after_first = LibraryMembership.objects.count()
        _run()
        self.assertEqual(LibraryMembership.objects.count(), count_after_first)

    def test_skips_libraries_with_existing_membership(self):
        # Pre-seed someone else as manager on lib-a; the command should
        # not overwrite or add a second row.
        other = User.objects.create_user(username="other", password="pw")
        LibraryMembership.objects.create(
            user=other,
            library_uid="lib-a",
            role=LibraryMembership.Role.MANAGER,
        )
        _run()

        # lib-a keeps its original manager-row; the superuser is NOT
        # granted on it because any membership existing is an opt-out.
        memberships_for_lib_a = LibraryMembership.objects.filter(
            library_uid="lib-a"
        )
        self.assertEqual(memberships_for_lib_a.count(), 1)
        self.assertEqual(memberships_for_lib_a.first().user, other)

        # lib-b had none, so the superuser got it.
        self.assertTrue(
            LibraryMembership.objects.filter(
                library_uid="lib-b", user=self.superuser
            ).exists()
        )

    def test_dry_run_does_not_persist(self):
        out = _run("--dry-run")
        self.assertIn("would insert", out)
        self.assertEqual(LibraryMembership.objects.count(), 0)

    def test_user_arg_overrides_default_superuser(self):
        target = User.objects.create_user(username="librarian", password="pw")
        _run("--user", "librarian")
        self.assertTrue(
            LibraryMembership.objects.filter(user=target).exists()
        )
        self.assertFalse(
            LibraryMembership.objects.filter(user=self.superuser).exists()
        )

    def test_unknown_user_raises_command_error(self):
        with self.assertRaises(CommandError):
            _run("--user", "nobody")

    def test_no_superuser_raises_command_error(self):
        self.superuser.is_superuser = False
        self.superuser.save(update_fields=["is_superuser"])
        with self.assertRaises(CommandError):
            _run()


class BackfillCommandEmptyNeo4jTest(TestCase):
    def setUp(self):
        self.superuser = User.objects.create_superuser(
            username="admin", password="pw", email="a@b.c"
        )
        self.patcher = mock.patch(
            "library.models.Library.nodes", new=_FakeNodes([])
        )
        self.patcher.start()
        self.addCleanup(self.patcher.stop)

    def test_no_libraries_is_noop(self):
        _run()
        self.assertEqual(LibraryMembership.objects.count(), 0)
