"""
Tests for the Open Library search engine.
Tests initialization, search functionality, and error handling.
"""

import pytest
from unittest.mock import Mock


class TestOpenLibrarySearchEngineInit:
    """Tests for Open Library search engine initialization."""

    def test_init_default_parameters(self):
        """Test initialization with default parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()

        assert engine.max_results == 10
        assert engine.sort == "relevance"
        assert engine.language is None
        assert engine.search_field is None
        assert engine.is_public is True
        assert engine.is_books is True
        assert engine.is_generic is False

    def test_init_custom_parameters(self):
        """Test initialization with custom parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(
            max_results=50,
            sort="new",
            language="eng",
            search_field="title",
        )

        assert engine.max_results == 50
        assert engine.sort == "new"
        assert engine.language == "eng"
        assert engine.search_field == "title"

    def test_base_url_set(self):
        """Test that API base URL is correctly set."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()
        assert engine.base_url == "https://openlibrary.org"
        assert engine.search_url == "https://openlibrary.org/search.json"

    def test_user_agent_header_set(self):
        """Test that User-Agent header is set."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()
        assert "User-Agent" in engine.headers
        assert "LocalDeepResearch" in engine.headers["User-Agent"]


class TestOpenLibraryQueryBuilding:
    """Tests for Open Library query parameter building."""

    def test_build_query_params_basic(self):
        """Test basic query params building."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=20)
        params = engine._build_query_params("lord of the rings")

        assert params["q"] == "lord of the rings"
        assert params["limit"] == 20
        assert "fields" in params

    def test_build_query_params_title_search(self):
        """Test query params with title search field."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(search_field="title")
        params = engine._build_query_params("1984")

        assert params["title"] == "1984"
        assert "q" not in params

    def test_build_query_params_author_search(self):
        """Test query params with author search field."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(search_field="author")
        params = engine._build_query_params("Tolkien")

        assert params["author"] == "Tolkien"
        assert "q" not in params

    def test_build_query_params_with_language(self):
        """Test query params with language filter."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(language="eng")
        params = engine._build_query_params("test")

        assert params["language"] == "eng"

    def test_build_query_params_with_sort(self):
        """Test query params with sort option."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(sort="new")
        params = engine._build_query_params("test")

        assert params["sort"] == "new"


class TestOpenLibraryCoverUrl:
    """Tests for Open Library cover URL generation."""

    def test_get_cover_url_with_id(self):
        """Test cover URL generation with valid ID."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()
        url = engine._get_cover_url(12345)

        assert url == "https://covers.openlibrary.org/b/id/12345-M.jpg"

    def test_get_cover_url_with_size(self):
        """Test cover URL generation with custom size."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()
        url = engine._get_cover_url(12345, size="L")

        assert url == "https://covers.openlibrary.org/b/id/12345-L.jpg"

    def test_get_cover_url_without_id(self):
        """Test cover URL generation without ID returns None."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()
        url = engine._get_cover_url(None)

        assert url is None


class TestOpenLibrarySearchExecution:
    """Tests for Open Library search execution."""

    @pytest.fixture
    def engine(self):
        """Create an Open Library engine."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        return OpenLibrarySearchEngine(max_results=10)

    def test_get_previews_success(self, engine, monkeypatch):
        """Test successful preview retrieval."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 100,
                "docs": [
                    {
                        "key": "/works/OL27448W",
                        "title": "The Lord of the Rings",
                        "author_name": ["J.R.R. Tolkien"],
                        "author_key": ["OL26320A"],
                        "first_publish_year": 1954,
                        "publisher": ["HarperCollins"],
                        "subject": ["Fantasy", "Middle Earth", "Hobbits"],
                        "isbn": ["9780261103252"],
                        "cover_i": 8743908,
                        "edition_count": 500,
                        "has_fulltext": True,
                        "ebook_access": "borrowable",
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("lord of the rings")

        assert len(previews) == 1
        assert previews[0]["title"] == "The Lord of the Rings"
        assert "J.R.R. Tolkien" in previews[0]["authors"]
        assert previews[0]["first_publish_year"] == 1954
        assert previews[0]["source"] == "Open Library"
        assert previews[0]["cover_url"] is not None

    def test_get_previews_empty_results(self, engine, monkeypatch):
        """Test preview retrieval with no results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(return_value={"num_found": 0, "docs": []})
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("xyznonexistentbook123")
        assert previews == []

    def test_get_previews_rate_limit_error(self, engine, monkeypatch):
        """Test that 429 errors raise RateLimitError."""
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        mock_response = Mock()
        mock_response.status_code = 429

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        with pytest.raises(RateLimitError):
            engine._get_previews("test query")

    def test_get_previews_handles_exception(self, engine, monkeypatch):
        """Test that exceptions are handled gracefully."""
        import requests

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(side_effect=requests.RequestException("Network error")),
        )

        previews = engine._get_previews("test query")
        assert previews == []

    def test_get_previews_passes_user_agent_header(self, engine, monkeypatch):
        """Test that User-Agent header is passed to API requests."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(return_value={"num_found": 0, "docs": []})
        mock_response.raise_for_status = Mock()

        mock_safe_get = Mock(return_value=mock_response)
        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            mock_safe_get,
        )

        engine._get_previews("test")

        # Verify headers were passed
        call_kwargs = mock_safe_get.call_args[1]
        assert "headers" in call_kwargs
        assert "User-Agent" in call_kwargs["headers"]


class TestOpenLibraryFullContent:
    """Tests for Open Library full content retrieval."""

    def test_get_full_content_builds_content(self):
        """Test that full content builds proper content string."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()

        items = [
            {
                "title": "Test Book",
                "authors": ["Author One", "Author Two"],
                "first_publish_year": 2020,
                "edition_count": 5,
                "subjects": ["Fiction", "Adventure"],
                "has_fulltext": True,
                "_raw": {
                    "language": ["eng", "fre"],
                    "subject": ["Fiction", "Adventure", "Fantasy"],
                    "publisher": ["Publisher One", "Publisher Two"],
                },
            }
        ]

        from unittest.mock import patch

        with patch.object(engine, "_fetch_work_details", return_value=None):
            results = engine._get_full_content(items)

        assert len(results) == 1
        assert "Authors: Author One, Author Two" in results[0]["content"]
        assert "First published: 2020" in results[0]["content"]
        assert "Subjects: Fiction, Adventure, Fantasy" in results[0]["content"]
        assert "Full text available" in results[0]["content"]
        assert "_raw" not in results[0]


class TestOpenLibraryEdgeCases:
    """Tests for Open Library edge cases and error handling."""

    @pytest.fixture
    def engine(self):
        """Create an Open Library engine."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        return OpenLibrarySearchEngine(max_results=10)

    def test_get_previews_unicode_in_title(self, engine, monkeypatch):
        """Test handling of Unicode characters in book title."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 1,
                "docs": [
                    {
                        "key": "/works/OL123W",
                        "title": "Les Misérables",
                        "author_name": ["Victor Hugo"],
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("les miserables")
        assert len(previews) == 1
        assert "Misérables" in previews[0]["title"]

    def test_get_previews_html_entities_in_title(self, engine, monkeypatch):
        """Test that HTML entities in title are decoded."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 1,
                "docs": [
                    {
                        "key": "/works/OL123W",
                        "title": "Harry Potter &amp; the Philosopher&#39;s Stone",
                        "author_name": ["J.K. Rowling"],
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("harry potter")
        assert len(previews) == 1
        # HTML entities should be decoded
        assert "&amp;" not in previews[0]["title"]
        assert "&" in previews[0]["title"]

    def test_get_previews_missing_optional_fields(self, engine, monkeypatch):
        """Test handling of missing optional fields."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 1,
                "docs": [
                    {
                        "key": "/works/OL123W",
                        "title": "Minimal Book",
                        # Missing: author_name, first_publish_year, publisher, etc.
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test")
        assert len(previews) == 1
        assert previews[0]["title"] == "Minimal Book"
        assert previews[0]["authors"] == []
        assert previews[0]["cover_url"] is None

    def test_get_previews_string_author_converted_to_list(
        self, engine, monkeypatch
    ):
        """Test that string author is converted to list."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 1,
                "docs": [
                    {
                        "key": "/works/OL123W",
                        "title": "Test Book",
                        "author_name": "Single Author",  # String instead of list
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test")
        assert len(previews) == 1
        assert isinstance(previews[0]["authors"], list)
        assert "Single Author" in previews[0]["authors"]

    def test_get_previews_limits_authors_to_five(self, engine, monkeypatch):
        """Test that authors are limited to 5."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "num_found": 1,
                "docs": [
                    {
                        "key": "/works/OL123W",
                        "title": "Multi-Author Book",
                        "author_name": [f"Author {i}" for i in range(10)],
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test")
        assert len(previews) == 1
        assert len(previews[0]["authors"]) == 5

    def test_get_full_content_without_raw(self, engine):
        """Test full content handling when _raw is missing."""
        items = [
            {
                "title": "Test Book",
                "authors": ["Test Author"],
            }
        ]

        results = engine._get_full_content(items)
        assert len(results) == 1
        # Should not crash

    def test_all_sort_options(self):
        """Test that various sort options can be used."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        sort_options = ["relevance", "new", "old", "random"]
        for sort in sort_options:
            engine = OpenLibrarySearchEngine(sort=sort)
            assert engine.sort == sort

    def test_all_search_fields(self):
        """Test that all search field options work."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        fields = [None, "title", "author", "subject"]
        for field in fields:
            engine = OpenLibrarySearchEngine(search_field=field)
            assert engine.search_field == field


class TestOpenLibraryHelperMethods:
    """Tests for Open Library helper methods."""

    def test_get_book_by_isbn_success(self, monkeypatch):
        """Test successful book retrieval by ISBN."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine()

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "title": "Test Book",
                "authors": [{"key": "/authors/OL123A"}],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(return_value=mock_response),
        )

        book = engine.get_book_by_isbn("9780261103252")

        assert book is not None
        assert book["title"] == "Test Book"

    def test_get_book_by_isbn_handles_error(self, monkeypatch):
        """Test that errors are handled in ISBN lookup."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )
        import requests

        engine = OpenLibrarySearchEngine()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
            Mock(side_effect=requests.RequestException("Not found")),
        )

        book = engine.get_book_by_isbn("invalid")
        assert book is None


@pytest.mark.integration
class TestOpenLibraryIntegration:
    """Integration tests making real API calls to Open Library."""

    def test_real_search_harry_potter(self):
        """Test real search for Harry Potter books."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=5)
        results = engine._get_previews("harry potter")

        # Verify we got results
        assert len(results) > 0, "Should find Harry Potter books"

        # Verify result structure
        for r in results:
            assert "title" in r, "Each result should have a title"
            assert "link" in r, "Each result should have a link"
            assert r["source"] == "Open Library", (
                "Source should be Open Library"
            )
            assert r["link"].startswith("https://openlibrary.org"), (
                "Link should be valid"
            )

        # Verify at least one result mentions Harry Potter
        titles = [r["title"].lower() for r in results]
        assert any("harry potter" in t or "potter" in t for t in titles), (
            f"At least one result should contain 'Harry Potter', got: {titles}"
        )

    def test_real_author_search_stephen_king(self):
        """Test real author search for Stephen King."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=5, search_field="author")
        results = engine._get_previews("Stephen King")

        # Verify we got results
        assert len(results) > 0, "Should find Stephen King books"

        # Verify at least one result has Stephen King as author
        found_king = False
        for r in results:
            authors = r.get("authors", [])
            for author in authors:
                if "king" in author.lower():
                    found_king = True
                    break
        assert found_king, (
            f"At least one result should have King as author, got: {[r.get('authors') for r in results]}"
        )

    def test_real_title_search_1984(self):
        """Test real title search for 1984."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=5, search_field="title")
        results = engine._get_previews("1984")

        # Verify we got results
        assert len(results) > 0, "Should find books with 1984 in title"

        # Verify result has expected fields
        first = results[0]
        assert "title" in first
        assert "authors" in first
        assert "link" in first

    def test_real_search_returns_metadata(self):
        """Test that real search returns proper metadata."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=3)
        results = engine._get_previews("lord of the rings tolkien")

        assert len(results) > 0, "Should find Lord of the Rings"

        # Check first result has expected metadata
        first = results[0]
        assert first.get("title"), "Should have title"
        assert first.get("authors"), "Should have authors"
        assert first.get("link"), "Should have link"
        assert first.get("id"), "Should have id"

        # Verify it's actually Tolkien
        authors = first.get("authors", [])
        assert any("tolkien" in a.lower() for a in authors), (
            f"Should find Tolkien as author, got: {authors}"
        )

    def test_real_search_with_language_filter(self):
        """Test real search with language filter."""
        from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
            OpenLibrarySearchEngine,
        )

        engine = OpenLibrarySearchEngine(max_results=5, language="eng")
        results = engine._get_previews("science fiction")

        assert len(results) > 0, "Should find science fiction books in English"
        assert all(r["source"] == "Open Library" for r in results)
