"""High-value edge case tests for research_library/deletion/utils/cascade_helper.py.

Covers gaps: filesystem special markers, FAISS partial deletion, blob None/empty,
zero-count returns, delete_document_completely ordering.
"""

import pytest
from unittest.mock import MagicMock, patch

from local_deep_research.constants import (
    FILE_PATH_METADATA_ONLY,
    FILE_PATH_TEXT_ONLY,
    FILE_PATH_BLOB_DELETED,
)
from local_deep_research.research_library.deletion.utils.cascade_helper import (
    CascadeHelper,
)


class TestDeleteFilesystemFile:
    """Edge cases for delete_filesystem_file."""

    def test_returns_false_for_empty_string(self):
        """Empty string is falsy, returns False."""
        assert CascadeHelper.delete_filesystem_file("") is False

    @pytest.mark.parametrize(
        "marker",
        [FILE_PATH_METADATA_ONLY, FILE_PATH_TEXT_ONLY, FILE_PATH_BLOB_DELETED],
    )
    def test_returns_false_for_each_special_marker(self, marker):
        """Each special marker individually returns False."""
        assert CascadeHelper.delete_filesystem_file(marker) is False

    def test_deletes_existing_file(self, tmp_path):
        """Existing file is deleted, returns True."""
        f = tmp_path / "test.txt"
        f.write_text("content")
        assert CascadeHelper.delete_filesystem_file(str(f)) is True
        assert not f.exists()

    def test_returns_false_for_nonexistent_file(self, tmp_path):
        """Nonexistent file returns False."""
        assert (
            CascadeHelper.delete_filesystem_file(str(tmp_path / "nope.txt"))
            is False
        )

    @patch(
        "local_deep_research.research_library.deletion.utils.cascade_helper.Path"
    )
    def test_returns_false_on_unlink_exception(self, mock_path_cls):
        """Returns False when unlink raises OSError."""
        mock_path = MagicMock()
        mock_path.is_file.return_value = True
        mock_path.unlink.side_effect = OSError("permission denied")
        mock_path_cls.return_value = mock_path
        assert CascadeHelper.delete_filesystem_file("/some/path") is False


class TestDeleteFaissIndexFiles:
    """Edge cases for delete_faiss_index_files."""

    def test_returns_false_for_none(self):
        assert CascadeHelper.delete_faiss_index_files(None) is False

    def test_returns_false_for_empty_string(self):
        assert CascadeHelper.delete_faiss_index_files("") is False

    def test_deletes_only_faiss_when_pkl_absent(self, tmp_path):
        """Deletes .faiss file and returns True when .pkl doesn't exist."""
        base = tmp_path / "index"
        faiss_file = base.with_suffix(".faiss")
        faiss_file.write_text("data")
        assert CascadeHelper.delete_faiss_index_files(str(base)) is True
        assert not faiss_file.exists()

    def test_deletes_only_pkl_when_faiss_absent(self, tmp_path):
        """Deletes .pkl file and returns True when .faiss doesn't exist."""
        base = tmp_path / "index"
        pkl_file = base.with_suffix(".pkl")
        pkl_file.write_text("data")
        assert CascadeHelper.delete_faiss_index_files(str(base)) is True
        assert not pkl_file.exists()

    def test_deletes_both_files(self, tmp_path):
        """Deletes both .faiss and .pkl files."""
        base = tmp_path / "index"
        base.with_suffix(".faiss").write_text("data")
        base.with_suffix(".pkl").write_text("data")
        assert CascadeHelper.delete_faiss_index_files(str(base)) is True
        assert not base.with_suffix(".faiss").exists()
        assert not base.with_suffix(".pkl").exists()

    def test_returns_false_when_no_files_exist(self, tmp_path):
        """Returns False when neither file exists."""
        base = tmp_path / "index"
        assert CascadeHelper.delete_faiss_index_files(str(base)) is False


class TestDeleteDocumentBlob:
    """Edge cases for delete_document_blob."""

    def test_no_blob_returns_zero(self):
        """No blob record returns 0."""
        session = MagicMock()
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )
        assert CascadeHelper.delete_document_blob(session, "doc1") == 0

    def test_blob_with_none_binary_returns_zero_and_deletes(self):
        """Blob with pdf_binary=None returns 0 but still deletes the record."""
        session = MagicMock()
        blob = MagicMock()
        blob.pdf_binary = None
        session.query.return_value.filter_by.return_value.first.return_value = (
            blob
        )
        result = CascadeHelper.delete_document_blob(session, "doc1")
        assert result == 0
        session.delete.assert_called_once_with(blob)


class TestGetDocumentBlobSize:
    """Edge cases for get_document_blob_size."""

    def test_no_blob_returns_zero(self):
        session = MagicMock()
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )
        assert CascadeHelper.get_document_blob_size(session, "doc1") == 0

    def test_blob_with_empty_bytes_returns_zero(self):
        """Blob with pdf_binary=b'' returns 0 (empty bytes is falsy)."""
        session = MagicMock()
        blob = MagicMock()
        blob.pdf_binary = b""
        session.query.return_value.filter_by.return_value.first.return_value = (
            blob
        )
        assert CascadeHelper.get_document_blob_size(session, "doc1") == 0

    def test_blob_with_content_returns_size(self):
        session = MagicMock()
        blob = MagicMock()
        blob.pdf_binary = b"x" * 1024
        session.query.return_value.filter_by.return_value.first.return_value = (
            blob
        )
        assert CascadeHelper.get_document_blob_size(session, "doc1") == 1024


class TestDeleteDocumentChunks:
    """Edge cases for delete_document_chunks."""

    def test_returns_zero_when_no_chunks(self):
        """Returns 0 when query deletes nothing."""
        session = MagicMock()
        session.query.return_value.filter.return_value.delete.return_value = 0
        result = CascadeHelper.delete_document_chunks(session, "doc1")
        assert result == 0

    def test_collection_name_none_no_extra_filter(self):
        """With collection_name=None, no additional filter is applied."""
        session = MagicMock()
        query = session.query.return_value.filter.return_value
        query.delete.return_value = 5
        CascadeHelper.delete_document_chunks(
            session, "doc1", collection_name=None
        )
        # filter called once (for source_id + source_type), not again for collection
        query.filter.assert_not_called()


class TestDeleteCollectionChunks:
    """Edge cases for delete_collection_chunks."""

    def test_returns_zero_when_empty_collection(self):
        session = MagicMock()
        session.query.return_value.filter_by.return_value.delete.return_value = 0
        assert CascadeHelper.delete_collection_chunks(session, "col1") == 0


class TestCountAndGetCollections:
    """Edge cases for count_document_in_collections and get_document_collections."""

    def test_count_returns_zero(self):
        session = MagicMock()
        session.query.return_value.filter_by.return_value.count.return_value = 0
        assert CascadeHelper.count_document_in_collections(session, "doc1") == 0

    def test_get_collections_returns_empty_list(self):
        session = MagicMock()
        session.query.return_value.filter_by.return_value.all.return_value = []
        assert CascadeHelper.get_document_collections(session, "doc1") == []


class TestDeleteDocumentCompletely:
    """Edge cases for delete_document_completely."""

    def test_returns_true_on_success(self):
        """Returns True when document row is deleted."""
        session = MagicMock()
        # All three queries succeed
        session.query.return_value.filter_by.return_value.delete.return_value = 1
        assert CascadeHelper.delete_document_completely(session, "doc1") is True

    def test_returns_false_when_document_row_absent(self):
        """Returns False when document query returns 0 even if blob deleted."""
        session = MagicMock()
        # Use side_effect to make third delete return 0
        session.query.return_value.filter_by.return_value.delete.side_effect = [
            1,
            1,
            0,
        ]
        assert (
            CascadeHelper.delete_document_completely(session, "doc1") is False
        )
