"""
Tests for the Project Gutenberg search engine via Gutendex API.
Tests initialization, search functionality, and error handling.
"""

import pytest
from unittest.mock import Mock


class TestGutenbergSearchEngineInit:
    """Tests for Gutenberg search engine initialization."""

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

        engine = GutenbergSearchEngine()

        assert engine.max_results == 10
        assert engine.languages is None
        assert engine.topic is None
        assert engine.sort == "popular"
        assert engine.is_public is True
        assert engine.is_books is True

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

        engine = GutenbergSearchEngine(
            max_results=20,
            languages="en,fr",
            topic="science fiction",
            sort="ascending",
        )

        assert engine.max_results == 20
        assert engine.languages == "en,fr"
        assert engine.topic == "science fiction"
        assert engine.sort == "ascending"

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

        engine = GutenbergSearchEngine()
        assert engine.base_url == "https://gutendex.com"
        assert engine.search_url == "https://gutendex.com/books/"

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

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


class TestGutenbergQueryBuilding:
    """Tests for Gutenberg 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_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        params = engine._build_query_params("sherlock holmes")

        assert params["search"] == "sherlock holmes"

    def test_build_query_params_with_languages(self):
        """Test query params with language filter."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(languages="en")
        params = engine._build_query_params("test")

        assert params["languages"] == "en"

    def test_build_query_params_with_topic(self):
        """Test query params with topic filter."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(topic="science fiction")
        params = engine._build_query_params("test")

        assert params["topic"] == "science fiction"


class TestGutenbergAuthorParsing:
    """Tests for Gutenberg author parsing."""

    def test_parse_authors_standard_format(self):
        """Test parsing authors in 'Last, First' format."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        authors = [{"name": "Doyle, Arthur Conan"}]

        result = engine._parse_authors(authors)
        assert result == ["Arthur Conan Doyle"]

    def test_parse_authors_simple_name(self):
        """Test parsing authors with simple name."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        authors = [{"name": "Anonymous"}]

        result = engine._parse_authors(authors)
        assert result == ["Anonymous"]

    def test_parse_authors_limits_to_five(self):
        """Test that author parsing limits to 5."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        authors = [{"name": f"Author{i}"} for i in range(10)]

        result = engine._parse_authors(authors)
        assert len(result) == 5


class TestGutenbergFormatSelection:
    """Tests for Gutenberg format URL selection."""

    def test_get_best_format_prefers_html(self):
        """Test that HTML is preferred format."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        formats = {
            "text/plain": "http://example.com/book.txt",
            "text/html": "http://example.com/book.html",
            "application/pdf": "http://example.com/book.pdf",
        }

        result = engine._get_best_format_url(formats)
        assert result == "http://example.com/book.html"

    def test_get_best_format_fallback(self):
        """Test fallback when no priority format available."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        formats = {"application/rdf+xml": "http://example.com/book.rdf"}

        result = engine._get_best_format_url(formats)
        assert result == "http://example.com/book.rdf"

    def test_get_best_format_empty(self):
        """Test with empty formats."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        result = engine._get_best_format_url({})
        assert result is None


class TestGutenbergSearchExecution:
    """Tests for Gutenberg search execution."""

    @pytest.fixture
    def engine(self):
        """Create a Gutenberg engine."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        return GutenbergSearchEngine(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={
                "count": 100,
                "results": [
                    {
                        "id": 1661,
                        "title": "The Adventures of Sherlock Holmes",
                        "authors": [{"name": "Doyle, Arthur Conan"}],
                        "subjects": ["Detective stories", "Mystery"],
                        "bookshelves": ["Detective Fiction"],
                        "languages": ["en"],
                        "download_count": 50000,
                        "formats": {
                            "text/html": "http://example.com/book.html",
                            "image/jpeg": "http://example.com/cover.jpg",
                        },
                        "copyright": False,
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

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

        previews = engine._get_previews("sherlock holmes")

        assert len(previews) == 1
        assert previews[0]["title"] == "The Adventures of Sherlock Holmes"
        assert "Arthur Conan Doyle" in previews[0]["authors"]
        assert previews[0]["source"] == "Project Gutenberg"
        assert "gutenberg.org" in previews[0]["link"]

    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_gutenberg.safe_get",
            Mock(return_value=mock_response),
        )

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

    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_gutenberg.safe_get",
            Mock(side_effect=requests.RequestException("Network error")),
        )

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


class TestGutenbergEdgeCases:
    """Tests for Gutenberg edge cases and error handling."""

    @pytest.fixture
    def engine(self):
        """Create a Gutenberg engine."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        return GutenbergSearchEngine(max_results=10)

    def test_get_previews_empty_results(self, engine, monkeypatch):
        """Test handling of empty search results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "count": 0,
                "results": [],
            }
        )
        mock_response.raise_for_status = Mock()

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

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

    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={
                "count": 1,
                "results": [
                    {
                        "id": 12345,
                        "title": "Les Misérables",
                        "authors": [{"name": "Hugo, Victor"}],
                        "subjects": ["French literature"],
                        "languages": ["fr"],
                        "download_count": 10000,
                        "formats": {},
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_gutenberg.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_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={
                "count": 1,
                "results": [
                    {
                        "id": 12345,
                        "title": "Minimal Book",
                        # Missing: authors, subjects, bookshelves, formats
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_gutenberg.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"] == []

    def test_get_previews_multiple_authors(self, engine, monkeypatch):
        """Test parsing of multiple authors."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={
                "count": 1,
                "results": [
                    {
                        "id": 12345,
                        "title": "Collaborative Work",
                        "authors": [
                            {"name": "Smith, John"},
                            {"name": "Doe, Jane"},
                            {"name": "Brown, Bob"},
                        ],
                        "formats": {},
                    }
                ],
            }
        )
        mock_response.raise_for_status = Mock()

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

        previews = engine._get_previews("test")
        assert len(previews) == 1
        assert len(previews[0]["authors"]) == 3
        assert "John Smith" in previews[0]["authors"]

    def test_all_sort_options(self):
        """Test that all sort options can be used."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        sort_options = ["popular", "ascending", "descending"]
        for sort in sort_options:
            engine = GutenbergSearchEngine(sort=sort)
            assert engine.sort == sort

    def test_format_priority_html_over_epub(self):
        """Test that HTML format is preferred over EPUB."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        formats = {
            "application/epub+zip": "http://example.com/book.epub",
            "text/html": "http://example.com/book.html",
        }

        result = engine._get_best_format_url(formats)
        # HTML should be preferred over EPUB for reading
        assert "html" in result

    def test_format_priority_epub_over_pdf(self):
        """Test that EPUB format is preferred over PDF."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()
        formats = {
            "application/pdf": "http://example.com/book.pdf",
            "application/epub+zip": "http://example.com/book.epub",
        }

        result = engine._get_best_format_url(formats)
        # EPUB should be preferred over PDF
        assert "epub" in result


class TestGutenbergFullContent:
    """Tests for Gutenberg 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_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine()

        items = [
            {
                "title": "Test Book",
                "authors": ["Author One"],
                "subjects": ["Fiction", "Adventure"],
                "bookshelves": ["Adventure"],
                "download_count": 1000,
                "read_url": "http://example.com/read",
                "_raw": {
                    "subjects": ["Fiction", "Adventure", "Classic"],
                    "bookshelves": ["Adventure", "Best Books"],
                    "translators": [],
                },
            }
        ]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert "Authors: Author One" in results[0]["content"]
        assert "Downloads: 1000" in results[0]["content"]
        assert "_raw" not in results[0]


@pytest.mark.integration
class TestGutenbergIntegration:
    """Integration tests making real API calls to Gutendex."""

    def test_real_search_sherlock_holmes(self):
        """Test real search for Sherlock Holmes."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(max_results=5)
        results = engine._get_previews("sherlock holmes")

        # Verify we got results
        assert len(results) > 0, "Should find Sherlock Holmes 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"] == "Project Gutenberg"
            assert "gutenberg.org" in r["link"], "Link should be Gutenberg URL"

        # Verify at least one is actually Sherlock Holmes
        titles = [r["title"].lower() for r in results]
        assert any("sherlock" in t or "holmes" in t for t in titles), (
            f"Should find Sherlock Holmes, got: {titles}"
        )

    def test_real_search_finds_doyle(self):
        """Test that Sherlock Holmes search finds Arthur Conan Doyle."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(max_results=5)
        results = engine._get_previews("sherlock holmes doyle")

        assert len(results) > 0, "Should find results"

        # At least one should have Doyle as author
        found_doyle = False
        for r in results:
            for author in r.get("authors", []):
                if "doyle" in author.lower():
                    found_doyle = True
                    break
        assert found_doyle, (
            f"Should find Doyle as author, got: {[r.get('authors') for r in results]}"
        )

    def test_real_search_pride_and_prejudice(self):
        """Test real search for Pride and Prejudice."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(max_results=5)
        results = engine._get_previews("pride and prejudice austen")

        assert len(results) > 0, "Should find Pride and Prejudice"

        # Check for Jane Austen
        found_austen = False
        for r in results:
            for author in r.get("authors", []):
                if "austen" in author.lower():
                    found_austen = True
                    break
        assert found_austen, (
            f"Should find Austen, got: {[r.get('authors') for r in results]}"
        )

    def test_real_search_returns_download_count(self):
        """Test that real search returns download counts."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(max_results=3)
        results = engine._get_previews("moby dick")

        assert len(results) > 0, "Should find Moby Dick"

        # Popular books should have download counts
        first = results[0]
        assert "download_count" in first, "Should have download_count"
        assert first["download_count"] > 0, "Popular book should have downloads"

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

        engine = GutenbergSearchEngine(max_results=5, languages="en")
        results = engine._get_previews("sherlock holmes")

        assert len(results) > 0, "Should find English Sherlock Holmes books"

        # All results should have English
        for r in results:
            assert "en" in r.get("languages", []), (
                f"Should be English, got languages: {r.get('languages')}"
            )

    def test_real_search_has_read_url(self):
        """Test that results include read URLs."""
        from local_deep_research.web_search_engines.engines.search_engine_gutenberg import (
            GutenbergSearchEngine,
        )

        engine = GutenbergSearchEngine(max_results=3)
        results = engine._get_previews("frankenstein")

        assert len(results) > 0, "Should find Frankenstein"

        # At least one should have a read URL
        has_read_url = any(r.get("read_url") for r in results)
        assert has_read_url, "At least one result should have read_url"
