"""
Coverage tests for CascadeHelper.

Targets paths not exercised by test_cascade_helper.py and
test_cascade_helper_edge_cases.py:
- delete_document_chunks: without collection_name (no 3rd filter call)
- delete_filesystem_file: exception during unlink → returns False
- delete_faiss_index_files: only .faiss exists (no .pkl)
- delete_faiss_index_files: only .pkl exists (no .faiss)
- delete_faiss_index_files: exception path → returns False
- delete_rag_indices_for_collection: no FAISS files deleted (index_path missing)
- update_download_tracker: no tracker row found → returns False
- update_download_tracker: exception in get_url_hash → returns False
- remove_from_faiss_index: faiss_index missing → returns False
- delete_document_completely: logs debug when deleted > 0
"""

from pathlib import Path
from unittest.mock import MagicMock, Mock, patch


MODULE = "local_deep_research.research_library.deletion.utils.cascade_helper"

from local_deep_research.research_library.deletion.utils.cascade_helper import (  # noqa: E402
    CascadeHelper,
)


# ---------------------------------------------------------------------------
# delete_document_chunks – without collection_name
# ---------------------------------------------------------------------------


class TestDeleteDocumentChunksNoCollection:
    def test_no_collection_does_not_add_extra_filter(self):
        mock_session = MagicMock()
        mock_query = MagicMock()
        mock_session.query.return_value = mock_query
        mock_query.filter.return_value = mock_query
        mock_query.delete.return_value = 7

        count = CascadeHelper.delete_document_chunks(mock_session, "doc-abc")

        assert count == 7
        # filter called once (source_id + source_type combined)
        assert mock_query.filter.call_count >= 1
        # Additional collection filter NOT called
        mock_query.filter_by.assert_not_called()


# ---------------------------------------------------------------------------
# delete_filesystem_file – exception path
# ---------------------------------------------------------------------------


class TestDeleteFilesystemFileException:
    def test_unlink_exception_returns_false(self, tmp_path):
        f = tmp_path / "important.pdf"
        f.write_bytes(b"data")

        with patch.object(
            Path, "unlink", side_effect=OSError("permission denied")
        ):
            result = CascadeHelper.delete_filesystem_file(str(f))

        assert result is False

    def test_empty_string_path_returns_false(self):
        result = CascadeHelper.delete_filesystem_file("")
        assert result is False


# ---------------------------------------------------------------------------
# delete_faiss_index_files – partial file existence
# ---------------------------------------------------------------------------


class TestDeleteFaissIndexFilesPartial:
    def test_only_faiss_file_returns_true(self, tmp_path):
        base = tmp_path / "idx"
        faiss = base.with_suffix(".faiss")
        faiss.write_bytes(b"faiss data")
        # No pkl file

        result = CascadeHelper.delete_faiss_index_files(str(base))

        assert result is True
        assert not faiss.exists()

    def test_only_pkl_file_returns_true(self, tmp_path):
        base = tmp_path / "idx"
        pkl = base.with_suffix(".pkl")
        pkl.write_bytes(b"pkl data")
        # No faiss file

        result = CascadeHelper.delete_faiss_index_files(str(base))

        assert result is True
        assert not pkl.exists()

    def test_exception_during_deletion_returns_false(self, tmp_path):
        base = tmp_path / "idx"
        faiss = base.with_suffix(".faiss")
        faiss.write_bytes(b"faiss data")

        with patch.object(Path, "unlink", side_effect=OSError("locked")):
            result = CascadeHelper.delete_faiss_index_files(str(base))

        assert result is False


# ---------------------------------------------------------------------------
# delete_rag_indices_for_collection – no FAISS files
# ---------------------------------------------------------------------------


class TestDeleteRagIndicesNoFaissFiles:
    def test_index_path_none_does_not_count_deleted_file(self):
        mock_session = MagicMock()
        mock_index = MagicMock()
        mock_index.index_path = None  # no file to delete

        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_index
        ]

        result = CascadeHelper.delete_rag_indices_for_collection(
            mock_session, "collection_xyz"
        )

        assert result["deleted_indices"] == 1
        assert result["deleted_files"] == 0


# ---------------------------------------------------------------------------
# update_download_tracker – no tracker found
# ---------------------------------------------------------------------------


class TestUpdateDownloadTrackerNoTracker:
    def test_tracker_not_found_returns_false(self):
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mock_doc = MagicMock()
        mock_doc.original_url = "https://example.com/paper.pdf"

        with patch(
            "local_deep_research.research_library.utils.get_url_hash",
            return_value="abc123",
        ):
            result = CascadeHelper.update_download_tracker(
                mock_session, mock_doc
            )

        assert result is False

    def test_exception_in_url_hash_returns_false(self):
        mock_session = MagicMock()
        mock_doc = MagicMock()
        mock_doc.original_url = "https://example.com/paper.pdf"

        with patch(
            "local_deep_research.research_library.utils.get_url_hash",
            side_effect=Exception("hash error"),
        ):
            result = CascadeHelper.update_download_tracker(
                mock_session, mock_doc
            )

        assert result is False


# ---------------------------------------------------------------------------
# remove_from_faiss_index – no faiss_index attribute
# ---------------------------------------------------------------------------


class TestRemoveFromFaissIndexNoop:
    def test_rag_service_without_faiss_index_returns_false(self):
        mock_rag = MagicMock(spec=[])  # spec=[] means no attributes

        mock_cm = MagicMock()
        mock_cm.__enter__ = Mock(return_value=mock_rag)
        mock_cm.__exit__ = Mock(return_value=False)

        with patch(
            "local_deep_research.research_library.services.library_rag_service.LibraryRAGService",
            return_value=mock_cm,
        ):
            result = CascadeHelper.remove_from_faiss_index(
                username="alice",
                collection_name="collection_abc",
                chunk_ids=["c1", "c2"],
            )

        assert result is False

    def test_exception_during_rag_service_init_returns_false(self):
        with patch(
            "local_deep_research.research_library.services.library_rag_service.LibraryRAGService",
            side_effect=Exception("rag init failed"),
        ):
            result = CascadeHelper.remove_from_faiss_index(
                username="bob",
                collection_name="collection_xyz",
                chunk_ids=["c1"],
            )

        assert result is False
