"""Tests for CascadeHelper utility."""

from unittest.mock import MagicMock, patch


from local_deep_research.constants import (
    FILE_PATH_SENTINELS,
)
from local_deep_research.research_library.deletion.utils.cascade_helper import (
    CascadeHelper,
)


class TestCascadeHelperDeleteDocumentChunks:
    """Tests for delete_document_chunks static method."""

    def test_deletes_chunks_for_document(self):
        """Should delete chunks for a specific document."""
        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 = 5

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

        assert count == 5
        mock_query.delete.assert_called_once()

    def test_filters_by_collection_when_provided(self):
        """Should filter by collection when specified."""
        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 = 3

        count = CascadeHelper.delete_document_chunks(
            mock_session, "doc-123", collection_name="collection_abc"
        )

        assert count == 3
        # Filter should be called multiple times (source_id, source_type, collection_name)
        assert mock_query.filter.call_count >= 2


class TestCascadeHelperDeleteCollectionChunks:
    """Tests for delete_collection_chunks static method."""

    def test_deletes_all_chunks_in_collection(self):
        """Should delete all chunks for a collection."""
        mock_session = MagicMock()
        mock_query = MagicMock()
        mock_session.query.return_value = mock_query
        mock_query.filter_by.return_value = mock_query
        mock_query.delete.return_value = 15

        count = CascadeHelper.delete_collection_chunks(
            mock_session, "collection_abc123"
        )

        assert count == 15


class TestCascadeHelperGetDocumentBlobSize:
    """Tests for get_document_blob_size static method."""

    def test_returns_blob_size(self):
        """Should return size of blob in bytes."""
        mock_session = MagicMock()
        mock_blob = MagicMock()
        mock_blob.pdf_binary = b"x" * 1024
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_blob

        size = CascadeHelper.get_document_blob_size(mock_session, "doc-123")

        assert size == 1024

    def test_returns_zero_when_no_blob(self):
        """Should return 0 when no blob exists."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        size = CascadeHelper.get_document_blob_size(mock_session, "doc-123")

        assert size == 0

    def test_returns_zero_when_blob_is_empty(self):
        """Should return 0 when blob has no content."""
        mock_session = MagicMock()
        mock_blob = MagicMock()
        mock_blob.pdf_binary = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_blob

        size = CascadeHelper.get_document_blob_size(mock_session, "doc-123")

        assert size == 0


class TestCascadeHelperDeleteDocumentBlob:
    """Tests for delete_document_blob static method."""

    def test_deletes_blob_and_returns_size(self):
        """Should delete blob and return its size."""
        mock_session = MagicMock()
        mock_blob = MagicMock()
        mock_blob.pdf_binary = b"x" * 2048
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_blob

        size = CascadeHelper.delete_document_blob(mock_session, "doc-123")

        assert size == 2048
        mock_session.delete.assert_called_with(mock_blob)

    def test_returns_zero_when_no_blob(self):
        """Should return 0 when no blob exists."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        size = CascadeHelper.delete_document_blob(mock_session, "doc-123")

        assert size == 0
        mock_session.delete.assert_not_called()


class TestCascadeHelperDeleteFilesystemFile:
    """Tests for delete_filesystem_file static method."""

    def test_deletes_existing_file(self, tmp_path):
        """Should delete file that exists."""
        test_file = tmp_path / "test.pdf"
        test_file.write_bytes(b"test content")

        result = CascadeHelper.delete_filesystem_file(str(test_file))

        assert result is True
        assert not test_file.exists()

    def test_returns_false_for_nonexistent_file(self, tmp_path):
        """Should return False for file that doesn't exist."""
        result = CascadeHelper.delete_filesystem_file(
            str(tmp_path / "nonexistent.pdf")
        )

        assert result is False

    def test_returns_false_for_none_path(self):
        """Should return False for None path."""
        result = CascadeHelper.delete_filesystem_file(None)

        assert result is False

    def test_skips_special_path_markers(self):
        """Should skip special path markers."""
        for marker in FILE_PATH_SENTINELS:
            result = CascadeHelper.delete_filesystem_file(marker)
            assert result is False


class TestCascadeHelperDeleteFaissIndexFiles:
    """Tests for delete_faiss_index_files static method."""

    def test_deletes_faiss_and_pkl_files(self, tmp_path):
        """Should delete both .faiss and .pkl files."""
        base_path = tmp_path / "index"
        faiss_file = base_path.with_suffix(".faiss")
        pkl_file = base_path.with_suffix(".pkl")

        faiss_file.write_bytes(b"faiss content")
        pkl_file.write_bytes(b"pkl content")

        result = CascadeHelper.delete_faiss_index_files(str(base_path))

        assert result is True
        assert not faiss_file.exists()
        assert not pkl_file.exists()

    def test_returns_false_for_none_path(self):
        """Should return False for None path."""
        result = CascadeHelper.delete_faiss_index_files(None)

        assert result is False

    def test_returns_false_when_no_files_exist(self, tmp_path):
        """Should return False when no files to delete."""
        result = CascadeHelper.delete_faiss_index_files(
            str(tmp_path / "nonexistent_index")
        )

        assert result is False


class TestCascadeHelperDeleteRagIndicesForCollection:
    """Tests for delete_rag_indices_for_collection static method."""

    def test_deletes_indices_and_files(self, tmp_path):
        """Should delete RAGIndex records and FAISS files."""
        mock_session = MagicMock()

        # Create mock index with file path
        mock_index = MagicMock()
        mock_index.index_path = str(tmp_path / "index")
        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_index
        ]

        # Create the files
        faiss_file = (tmp_path / "index").with_suffix(".faiss")
        faiss_file.write_bytes(b"faiss content")

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

        assert result["deleted_indices"] == 1
        mock_session.delete.assert_called_with(mock_index)

    def test_returns_zero_when_no_indices(self):
        """Should return 0 when no indices exist."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.all.return_value = []

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

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


class TestCascadeHelperUpdateDownloadTracker:
    """Tests for update_download_tracker static method."""

    def test_updates_tracker_when_found(self):
        """Should update tracker when document has URL."""
        mock_session = MagicMock()
        mock_doc = MagicMock()
        mock_doc.original_url = "https://example.com/paper.pdf"

        mock_tracker = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_tracker

        # Mock get_url_hash at the correct location (inside the method)
        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 True
        assert mock_tracker.is_downloaded is False

    def test_returns_false_when_no_url(self):
        """Should return False when document has no URL."""
        mock_session = MagicMock()
        mock_doc = MagicMock()
        mock_doc.original_url = None

        result = CascadeHelper.update_download_tracker(mock_session, mock_doc)

        assert result is False


class TestCascadeHelperCountDocumentInCollections:
    """Tests for count_document_in_collections static method."""

    def test_returns_collection_count(self):
        """Should return number of collections document is in."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.count.return_value = 3

        count = CascadeHelper.count_document_in_collections(
            mock_session, "doc-123"
        )

        assert count == 3


class TestCascadeHelperGetDocumentCollections:
    """Tests for get_document_collections static method."""

    def test_returns_collection_ids(self):
        """Should return list of collection IDs."""
        mock_session = MagicMock()

        mock_dc1 = MagicMock()
        mock_dc1.collection_id = "col-1"
        mock_dc2 = MagicMock()
        mock_dc2.collection_id = "col-2"

        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_dc1,
            mock_dc2,
        ]

        collections = CascadeHelper.get_document_collections(
            mock_session, "doc-123"
        )

        assert collections == ["col-1", "col-2"]


class TestCascadeHelperDeleteDocumentCompletely:
    """Tests for delete_document_completely static method."""

    def test_deletes_document_and_related_records(self):
        """Should delete document, blob, and collection links."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.delete.return_value = 1

        result = CascadeHelper.delete_document_completely(
            mock_session, "doc-123"
        )

        assert result is True
        # Should have called delete for blob, collection links, and document
        assert (
            mock_session.query.return_value.filter_by.return_value.delete.call_count
            >= 3
        )

    def test_returns_false_when_document_not_found(self):
        """Should return False when document doesn't exist."""
        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.delete.return_value = 0

        result = CascadeHelper.delete_document_completely(
            mock_session, "nonexistent"
        )

        assert result is False
