"""
Tests for workspace scoping in SearchRequest and the Cypher scope clause.

These exercise the dataclass-level normalization and the construction
of Cypher parameter dicts. The actual Cypher execution against Neo4j
is validated by the manual end-to-end test plan.
"""

from django.test import TestCase

from library.services.search import _WORKSPACE_SCOPE_CLAUSE, SearchRequest


class SearchRequestScopingTests(TestCase):
    """SearchRequest workspace_id behavior."""

    def test_default_workspace_id_is_none(self):
        req = SearchRequest(query="hello")
        self.assertIsNone(req.workspace_id)

    def test_explicit_workspace_id_preserved(self):
        req = SearchRequest(query="hello", workspace_id="ws_abc")
        self.assertEqual(req.workspace_id, "ws_abc")

    def test_empty_string_workspace_id_normalized_to_none(self):
        """Empty strings must NOT slip through as a truthy filter at the Cypher boundary."""
        req = SearchRequest(query="hello", workspace_id="")
        self.assertIsNone(req.workspace_id)

    def test_empty_string_library_uid_normalized_to_none(self):
        req = SearchRequest(query="hello", library_uid="")
        self.assertIsNone(req.library_uid)

    def test_empty_string_library_type_normalized_to_none(self):
        req = SearchRequest(query="hello", library_type="")
        self.assertIsNone(req.library_type)

    def test_empty_string_collection_uid_normalized_to_none(self):
        req = SearchRequest(query="hello", collection_uid="")
        self.assertIsNone(req.collection_uid)


class WorkspaceScopeClauseTests(TestCase):
    """Sanity checks on the Cypher snippet itself.

    The clause must produce two distinct, non-overlapping result sets:
    1. workspace_id IS NULL → only global libraries (lib.workspace_id IS NULL)
    2. workspace_id = X      → only libraries with workspace_id = X

    A "leaks both" bug would be a Cypher OR that fails to bracket properly.
    Verifying the literal string here is a cheap regression guard against
    refactors that accidentally change the operator precedence.
    """

    def test_clause_references_lib_workspace_id(self):
        self.assertIn("lib.workspace_id", _WORKSPACE_SCOPE_CLAUSE)

    def test_clause_references_workspace_id_param(self):
        self.assertIn("$workspace_id", _WORKSPACE_SCOPE_CLAUSE)

    def test_clause_handles_both_modes(self):
        """Both 'IS NULL' and '=' branches must be present."""
        self.assertIn("IS NULL", _WORKSPACE_SCOPE_CLAUSE)
        self.assertIn("=", _WORKSPACE_SCOPE_CLAUSE)

    def test_clause_starts_with_AND_so_it_appends_safely(self):
        """The clause is appended to existing WHERE filters."""
        self.assertTrue(
            _WORKSPACE_SCOPE_CLAUSE.lstrip().startswith("AND"),
            f"Clause must start with AND: {_WORKSPACE_SCOPE_CLAUSE!r}",
        )
