"""Tests for the plain library REST endpoints beyond create.

Currently covers the DELETE cascades: library, collection, and item DELETE
endpoints must go through the shared ``library_delete`` service functions —
bare ``.delete()`` calls leak child nodes (Collections, Items, Chunks,
Images) and, for libraries, skip orphan-Concept GC. Neo4j is stubbed via
``sys.modules``, same style as ``test_managed_by.py``.
"""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import MagicMock, patch

from django.contrib.auth import get_user_model
from django.test import TestCase
from rest_framework.test import APIClient

User = get_user_model()


def _fake_node_cls(instance):
    """A neomodel-class stand-in whose ``nodes.get`` returns ``instance``.

    ``instance=None`` makes ``nodes.get`` raise the class's DoesNotExist.
    """
    fake_nodes = MagicMock()

    class DoesNotExist(Exception):
        pass

    if instance is None:
        fake_nodes.get.side_effect = DoesNotExist()
    else:
        fake_nodes.get.return_value = instance
    return SimpleNamespace(nodes=fake_nodes, DoesNotExist=DoesNotExist)


class LibraryApiDeleteTests(TestCase):
    """DELETE on the plain library endpoint cascades."""

    def setUp(self):
        self.user = User.objects.create_user(username="op", password="pw")
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def _fake_models_module(self, lib):
        return SimpleNamespace(Library=_fake_node_cls(lib))

    def test_delete_uses_shared_cascade(self):
        lib = SimpleNamespace(uid="lib-1", name="Docs")
        cascade_result = {
            "library_uid": "lib-1",
            "name": "Docs",
            "item_count": 3,
            "item_s3_keys": [],
            "orphans_deleted": 1,
        }
        with patch.dict(
            "sys.modules", {"library.models": self._fake_models_module(lib)}
        ), patch(
            "library.api.views.delete_library_cascade",
            return_value=cascade_result,
        ) as mock_cascade:
            response = self.client.delete("/library/api/libraries/lib-1/")

        self.assertEqual(response.status_code, 204)
        mock_cascade.assert_called_once_with(lib)

    def test_delete_missing_library_returns_404(self):
        with patch.dict(
            "sys.modules", {"library.models": self._fake_models_module(None)}
        ), patch("library.api.views.delete_library_cascade") as mock_cascade:
            response = self.client.delete("/library/api/libraries/nope/")

        self.assertEqual(response.status_code, 404)
        mock_cascade.assert_not_called()


class CollectionApiDeleteTests(TestCase):
    """DELETE on the collection endpoint cascades Items/Chunks/Images."""

    def setUp(self):
        self.user = User.objects.create_user(username="op", password="pw")
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_delete_uses_shared_cascade(self):
        col = SimpleNamespace(uid="col-1", name="Default")
        with patch.dict(
            "sys.modules",
            {"library.models": SimpleNamespace(Collection=_fake_node_cls(col))},
        ), patch(
            "library.api.views.delete_collection_cascade",
            return_value={
                "collection_uid": "col-1",
                "name": "Default",
                "item_count": 2,
                "item_s3_keys": [],
            },
        ) as mock_cascade:
            response = self.client.delete("/library/api/collections/col-1/")

        self.assertEqual(response.status_code, 204)
        mock_cascade.assert_called_once_with(col)

    def test_delete_missing_collection_returns_404(self):
        with patch.dict(
            "sys.modules",
            {"library.models": SimpleNamespace(Collection=_fake_node_cls(None))},
        ), patch("library.api.views.delete_collection_cascade") as mock_cascade:
            response = self.client.delete("/library/api/collections/nope/")

        self.assertEqual(response.status_code, 404)
        mock_cascade.assert_not_called()


class ItemApiDeleteTests(TestCase):
    """DELETE on the item endpoint cascades Chunks/Images/embeddings."""

    def setUp(self):
        self.user = User.objects.create_user(username="op", password="pw")
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_delete_uses_shared_cascade(self):
        item = SimpleNamespace(uid="item-1", title="Doc")
        with patch.dict(
            "sys.modules",
            {"library.models": SimpleNamespace(Item=_fake_node_cls(item))},
        ), patch(
            "library.api.views.delete_item_cascade",
            return_value={"item_uid": "item-1", "s3_key": ""},
        ) as mock_cascade:
            response = self.client.delete("/library/api/items/item-1/")

        self.assertEqual(response.status_code, 204)
        mock_cascade.assert_called_once_with("item-1")

    def test_delete_missing_item_returns_404(self):
        with patch.dict(
            "sys.modules",
            {"library.models": SimpleNamespace(Item=_fake_node_cls(None))},
        ), patch("library.api.views.delete_item_cascade") as mock_cascade:
            response = self.client.delete("/library/api/items/nope/")

        self.assertEqual(response.status_code, 404)
        mock_cascade.assert_not_called()
