"""Coverage tests for DocumentDeletionService.

Covers uncovered paths: filesystem deletion, title fallbacks, orphaned documents,
multiple collections, exception handling in blob deletion, filesystem errors, etc.
"""

from unittest.mock import MagicMock, Mock, patch


from local_deep_research.research_library.deletion.services.document_deletion import (
    DocumentDeletionService,
)

MODULE_PATH = (
    "local_deep_research.research_library.deletion.services.document_deletion"
)
UTILS_PATH = "local_deep_research.research_library.utils"


def _make_session_context():
    """Create a mock session context manager."""
    mock_session = MagicMock()
    mock_cm = MagicMock()
    mock_cm.__enter__ = Mock(return_value=mock_session)
    mock_cm.__exit__ = Mock(return_value=None)
    return mock_session, mock_cm


class TestDeleteDocumentFilesystem:
    """Tests for delete_document with filesystem storage mode."""

    def test_deletes_filesystem_file_when_storage_mode_filesystem(self):
        """Should delete filesystem file when storage_mode is 'filesystem'."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "FS Document"
            mock_doc.filename = "test.pdf"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path/test.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = ["col-1"]
                mock_helper.delete_document_chunks.return_value = 2
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_filesystem_file.return_value = True
                mock_helper.delete_document_completely.return_value = True

                mock_abs_path = MagicMock()
                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    return_value=mock_abs_path,
                ):
                    result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        assert result["file_deleted"] is True
        assert result["blob_deleted"] is False

    def test_filesystem_file_path_none_returns_no_deletion(self):
        """Should not attempt file deletion when get_absolute_path_from_settings returns None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "FS Doc"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    return_value=None,
                ):
                    result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        assert result["file_deleted"] is False

    def test_filesystem_deletion_exception_handled(self):
        """Should handle exception during filesystem file deletion gracefully."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "FS Doc"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    side_effect=Exception("path resolution error"),
                ):
                    result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        assert result["file_deleted"] is False


class TestDeleteDocumentTitleFallbacks:
    """Tests for title fallback logic in delete_document."""

    def test_uses_filename_when_title_is_none(self):
        """Should use filename when title is None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = None
            mock_doc.filename = "myfile.pdf"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["title"] == "myfile.pdf"

    def test_uses_untitled_when_both_title_and_filename_are_none(self):
        """Should use 'Untitled' when both title and filename are None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = None
            mock_doc.filename = None
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["title"] == "Untitled"


class TestDeleteDocumentMultipleCollections:
    """Tests for delete_document with multiple collections."""

    def test_deletes_chunks_for_multiple_collections(self):
        """Should delete chunks from all collections the document is in."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "Multi-col Doc"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = [
                    "col-1",
                    "col-2",
                    "col-3",
                ]
                mock_helper.delete_document_chunks.side_effect = [3, 5, 2]
                mock_helper.get_document_blob_size.return_value = 512
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        assert result["chunks_deleted"] == 10
        assert result["collections_unlinked"] == 3
        assert result["blob_deleted"] is True
        assert result["blob_size"] == 512
        assert mock_helper.delete_document_chunks.call_count == 3

    def test_blob_size_zero_means_blob_not_deleted(self):
        """Should set blob_deleted=False when blob_size is 0."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "No Blob"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["blob_deleted"] is False
        assert result["blob_size"] == 0


class TestDeleteDocumentExceptionRollback:
    """Tests for delete_document exception handling."""

    def test_rollback_on_cascade_helper_exception(self):
        """Should rollback session when CascadeHelper raises."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "Error Doc"
            mock_doc.storage_mode = "database"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.side_effect = Exception(
                    "cascade error"
                )
                result = service.delete_document("doc-12345678")

        assert result["deleted"] is False
        assert "error" in result
        mock_session.rollback.assert_called_once()

    def test_commit_called_on_success(self):
        """Should call session.commit() on successful deletion."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "Good Doc"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        mock_session.commit.assert_called_once()


class TestDeleteBlobOnlyFilesystem:
    """Tests for delete_blob_only with filesystem storage."""

    def test_deletes_filesystem_blob_successfully(self):
        """Should delete filesystem file and report bytes freed."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/path/to/doc.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            mock_file_path = MagicMock()
            mock_file_path.is_file.return_value = True
            mock_file_path.stat.return_value.st_size = 4096

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    return_value=mock_file_path,
                ):
                    result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert result["bytes_freed"] == 4096
        assert result["storage_mode_updated"] is True
        assert mock_doc.storage_mode == "none"
        mock_helper.delete_filesystem_file.assert_called_once()

    def test_filesystem_blob_no_file_path(self):
        """Should handle filesystem mode with no file_path."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = None
            mock_session.query.return_value.get.return_value = mock_doc

            result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert result["bytes_freed"] == 0
        assert result["storage_mode_updated"] is True

    def test_filesystem_blob_file_not_found(self):
        """Should handle when filesystem file does not exist."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/path/to/missing.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            mock_file_path = MagicMock()
            mock_file_path.is_file.return_value = False

            with patch(
                f"{UTILS_PATH}.get_absolute_path_from_settings",
                return_value=mock_file_path,
            ):
                result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert result["bytes_freed"] == 0
        assert result["storage_mode_updated"] is True

    def test_filesystem_blob_path_resolution_returns_none(self):
        """Should handle when path resolution returns None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(
                f"{UTILS_PATH}.get_absolute_path_from_settings",
                return_value=None,
            ):
                result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert result["bytes_freed"] == 0

    def test_filesystem_blob_exception_during_file_delete(self):
        """Should handle exception during filesystem file deletion."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(
                f"{UTILS_PATH}.get_absolute_path_from_settings",
                side_effect=Exception("disk error"),
            ):
                result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert result["bytes_freed"] == 0
        assert result["storage_mode_updated"] is True


class TestDeleteBlobOnlyException:
    """Tests for delete_blob_only exception handling."""

    def test_rollback_on_exception(self):
        """Should rollback and return error on unexpected exception."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "database"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_blob.side_effect = Exception("boom")
                result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is False
        assert result["bytes_freed"] == 0
        assert "error" in result
        mock_session.rollback.assert_called_once()

    def test_updates_file_path_to_blob_deleted_sentinel(self):
        """Should set file_path to FILE_PATH_BLOB_DELETED constant."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = "/original/path.pdf"
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_blob.return_value = 1000

                result = service.delete_blob_only("doc-12345678")

        assert result["deleted"] is True
        assert mock_doc.file_path == "blob_deleted"
        assert mock_doc.storage_mode == "none"


class TestRemoveFromCollectionOrphaned:
    """Tests for remove_from_collection when document becomes orphaned."""

    def test_deletes_orphaned_document_completely(self):
        """Should delete document completely when it's no longer in any collection."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "database"
            mock_doc.file_path = None
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 5
                mock_helper.count_document_in_collections.return_value = 0

                result = service.remove_from_collection(
                    "doc-12345678", "col-12345678"
                )

        assert result["unlinked"] is True
        assert result["document_deleted"] is True
        mock_helper.update_download_tracker.assert_called_once()
        mock_helper.delete_document_completely.assert_called_once()

    def test_deletes_orphaned_document_with_filesystem_file(self):
        """Should delete filesystem file when orphaned document uses filesystem storage."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/path/to/file.pdf"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            mock_abs_path = MagicMock()
            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 2
                mock_helper.count_document_in_collections.return_value = 0
                mock_helper.delete_filesystem_file.return_value = True

                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    return_value=mock_abs_path,
                ):
                    result = service.remove_from_collection(
                        "doc-12345678", "col-12345678"
                    )

        assert result["unlinked"] is True
        assert result["document_deleted"] is True
        mock_helper.delete_filesystem_file.assert_called_once()

    def test_orphaned_filesystem_deletion_exception_handled(self):
        """Should handle exception during orphaned filesystem file deletion."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/path/to/file.pdf"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 0
                mock_helper.count_document_in_collections.return_value = 0

                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    side_effect=Exception("path error"),
                ):
                    result = service.remove_from_collection(
                        "doc-12345678", "col-12345678"
                    )

        assert result["unlinked"] is True
        assert result["document_deleted"] is True

    def test_orphaned_filesystem_path_returns_none(self):
        """Should handle None returned from get_absolute_path_from_settings."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = "/some/path.pdf"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 0
                mock_helper.count_document_in_collections.return_value = 0

                with patch(
                    f"{UTILS_PATH}.get_absolute_path_from_settings",
                    return_value=None,
                ):
                    result = service.remove_from_collection(
                        "doc-12345678", "col-12345678"
                    )

        assert result["unlinked"] is True
        assert result["document_deleted"] is True


class TestRemoveFromCollectionException:
    """Tests for remove_from_collection exception handling."""

    def test_rollback_on_exception(self):
        """Should rollback and return error on unexpected exception."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.side_effect = Exception(
                    "db error"
                )
                result = service.remove_from_collection(
                    "doc-12345678", "col-12345678"
                )

        assert result["unlinked"] is False
        assert result["document_deleted"] is False
        assert "error" in result
        mock_session.rollback.assert_called_once()

    def test_remaining_collections_prevents_full_deletion(self):
        """Should not delete document when it's still in other collections."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 3
                mock_helper.count_document_in_collections.return_value = 2

                result = service.remove_from_collection(
                    "doc-12345678", "col-12345678"
                )

        assert result["unlinked"] is True
        assert result["document_deleted"] is False
        mock_helper.delete_document_completely.assert_not_called()

    def test_session_commit_called_on_success(self):
        """Should call session.commit on successful removal."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 0
                mock_helper.count_document_in_collections.return_value = 1

                result = service.remove_from_collection(
                    "doc-12345678", "col-12345678"
                )

        assert result["unlinked"] is True
        mock_session.commit.assert_called_once()
        mock_session.delete.assert_called_once_with(mock_doc_collection)


class TestGetDeletionPreviewEdgeCases:
    """Tests for get_deletion_preview edge cases."""

    def test_uses_filename_when_title_is_none(self):
        """Should use filename as title fallback in preview."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = None
            mock_doc.filename = "report.pdf"
            mock_doc.file_type = "pdf"
            mock_doc.storage_mode = "database"
            mock_doc.text_content = "some text"
            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter.return_value.count.return_value = 5

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = ["col-1"]
                mock_helper.get_document_blob_size.return_value = 2048

                result = service.get_deletion_preview("doc-12345678")

        assert result["found"] is True
        assert result["title"] == "report.pdf"

    def test_uses_untitled_when_both_none(self):
        """Should use 'Untitled' when both title and filename are None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = None
            mock_doc.filename = None
            mock_doc.file_type = "pdf"
            mock_doc.storage_mode = "none"
            mock_doc.text_content = None
            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter.return_value.count.return_value = 0

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0

                result = service.get_deletion_preview("doc-12345678")

        assert result["found"] is True
        assert result["title"] == "Untitled"
        assert result["has_blob"] is False
        assert result["has_text"] is False
        assert result["collections_count"] == 0
        assert result["chunks_count"] == 0

    def test_has_text_false_when_text_content_empty_string(self):
        """Should report has_text=False when text_content is empty string."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "A Title"
            mock_doc.filename = "file.pdf"
            mock_doc.file_type = "pdf"
            mock_doc.storage_mode = "database"
            mock_doc.text_content = ""
            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter.return_value.count.return_value = 0

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0

                result = service.get_deletion_preview("doc-12345678")

        assert result["has_text"] is False

    def test_preview_with_large_blob_and_many_chunks(self):
        """Should correctly report large blob size and chunk count."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "Big Doc"
            mock_doc.filename = "big.pdf"
            mock_doc.file_type = "pdf"
            mock_doc.storage_mode = "database"
            mock_doc.text_content = "lots of text here"
            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter.return_value.count.return_value = 500

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = [
                    "c1",
                    "c2",
                    "c3",
                    "c4",
                ]
                mock_helper.get_document_blob_size.return_value = 10_000_000

                result = service.get_deletion_preview("doc-12345678")

        assert result["found"] is True
        assert result["has_blob"] is True
        assert result["blob_size"] == 10_000_000
        assert result["chunks_count"] == 500
        assert result["collections_count"] == 4
        assert result["has_text"] is True


class TestDeleteDocumentNoFilePathOnFilesystemMode:
    """Test edge case: filesystem storage mode but file_path is None."""

    def test_skips_filesystem_deletion_when_file_path_none(self):
        """Should skip filesystem file deletion when file_path is None."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.title = "FS No Path"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = None  # No file path set
            mock_session.query.return_value.get.return_value = mock_doc

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.get_document_collections.return_value = []
                mock_helper.get_document_blob_size.return_value = 0
                mock_helper.delete_document_completely.return_value = True

                result = service.delete_document("doc-12345678")

        assert result["deleted"] is True
        assert result["file_deleted"] is False


class TestRemoveFromCollectionOrphanedNoFilePath:
    """Tests orphaned doc with filesystem storage but no file_path."""

    def test_orphaned_no_file_path_skips_file_deletion(self):
        """Should skip file deletion for orphaned doc without file_path."""
        service = DocumentDeletionService(username="testuser")
        mock_session, mock_cm = _make_session_context()

        with patch(f"{MODULE_PATH}.get_user_db_session", return_value=mock_cm):
            mock_doc = MagicMock()
            mock_doc.id = "doc-12345678"
            mock_doc.storage_mode = "filesystem"
            mock_doc.file_path = None
            mock_doc_collection = MagicMock()

            mock_session.query.return_value.get.return_value = mock_doc
            mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc_collection

            with patch(f"{MODULE_PATH}.CascadeHelper") as mock_helper:
                mock_helper.delete_document_chunks.return_value = 0
                mock_helper.count_document_in_collections.return_value = 0

                result = service.remove_from_collection(
                    "doc-12345678", "col-12345678"
                )

        assert result["unlinked"] is True
        assert result["document_deleted"] is True
        mock_helper.delete_filesystem_file.assert_not_called()
