"""
Comprehensive tests for Wikipedia MCP server tools.
"""

import asyncio
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
from wikipedia_mcp.server import create_server
from wikipedia_mcp.wikipedia_client import WikipediaClient
from tests.tool_helpers import get_tools


class TestWikipediaClient:
    """Test the WikipediaClient class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.client = WikipediaClient()

    @patch("wikipedia_mcp.wikipedia_client.requests.get")
    def test_search_success(self, mock_get):
        """Test successful search operation."""
        # Mock API response
        mock_response = Mock()
        mock_response.raise_for_status.return_value = None
        mock_response.json.return_value = {
            "query": {
                "search": [
                    {
                        "title": "Python (programming language)",
                        "snippet": "Python is a programming language",
                        "pageid": 12345,
                        "wordcount": 1000,
                        "timestamp": "2023-01-01T00:00:00Z",
                    }
                ]
            }
        }
        mock_get.return_value = mock_response

        # Test search
        results = self.client.search("Python", limit=1)

        # Assertions
        assert len(results) == 1
        assert results[0]["title"] == "Python (programming language)"
        assert results[0]["snippet"] == "Python is a programming language"
        assert results[0]["pageid"] == 12345
        mock_get.assert_called_once()

    @patch("wikipedia_mcp.wikipedia_client.requests.get")
    def test_search_failure(self, mock_get):
        """Test handling of search failures when exceptions occur."""
        mock_get.side_effect = Exception("API error")
        results = self.client.search("Python")
        assert results == []

    @patch("wikipedia_mcp.wikipedia_client.requests.get")
    def test_search_empty_query(self, mock_get):
        """Empty queries should not trigger HTTP calls."""
        results = self.client.search("")
        assert results == []
        mock_get.assert_not_called()

        results = self.client.search("   ")
        assert results == []
        mock_get.assert_not_called()

    @patch("wikipedia_mcp.wikipedia_client.requests.get")
    def test_search_limit_validation(self, mock_get):
        """Ensure the limit is bounded within API constraints."""
        mock_response = Mock()
        mock_response.raise_for_status.return_value = None
        mock_response.json.return_value = {"query": {"search": []}}
        mock_get.return_value = mock_response

        self.client.search("Python", limit=1000)
        _, kwargs = mock_get.call_args
        assert kwargs["params"]["srlimit"] == 500

    @patch("wikipedia_mcp.wikipedia_client.requests.get")
    def test_search_api_error(self, mock_get):
        """Handle API error payloads gracefully."""
        mock_response = Mock()
        mock_response.raise_for_status.return_value = None
        mock_response.json.return_value = {"error": {"code": "badquery", "info": "Invalid query"}}
        mock_get.return_value = mock_response

        results = self.client.search("Python")
        assert results == []

    @patch("wikipedia_mcp.wikipedia_client.WikipediaClient._extract_sections")
    def test_get_article_success(self, mock_extract_sections):
        """Test successful article retrieval."""
        # Mock Wikipedia page
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Python (programming language)"
        mock_page.pageid = 12345
        mock_page.summary = "Python is a programming language"
        mock_page.text = "Full article text here"
        mock_page.fullurl = "https://en.wikipedia.org/wiki/Python_(programming_language)"
        mock_page.categories = {"Category:Programming languages": None}
        mock_page.links = {"Link1": None, "Link2": None}
        mock_page.sections = []

        mock_extract_sections.return_value = [{"title": "History", "level": 0, "text": "History text", "sections": []}]

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            article = self.client.get_article("Python (programming language)")

        assert article["exists"] is True
        assert article["title"] == "Python (programming language)"
        assert article["pageid"] == 12345
        assert article["summary"] == "Python is a programming language"
        assert article["text"] == "Full article text here"
        assert len(article["categories"]) == 1
        assert len(article["links"]) == 2

    def test_get_article_not_found(self):
        """Test article retrieval for non-existent page."""
        mock_page = Mock()
        mock_page.exists.return_value = False

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            article = self.client.get_article("NonExistentPage")

        assert article["exists"] is False
        assert article["error"] == "Page does not exist"

    def test_get_summary_success(self):
        """Test successful summary retrieval."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.summary = "This is a test summary"

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.get_summary("Test Page")

        assert summary == "This is a test summary"

    def test_get_summary_not_found(self):
        """Test summary retrieval for non-existent page."""
        mock_page = Mock()
        mock_page.exists.return_value = False

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.get_summary("NonExistentPage")

        assert "No Wikipedia article found for 'NonExistentPage'" in summary

    @patch("wikipedia_mcp.wikipedia_client.WikipediaClient._extract_sections")
    def test_get_sections_success(self, mock_extract_sections):
        """Test successful sections retrieval."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.sections = []

        mock_extract_sections.return_value = [
            {"title": "History", "level": 0, "text": "History text", "sections": []},
            {"title": "Features", "level": 0, "text": "Features text", "sections": []},
        ]

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            sections = self.client.get_sections("Test Page")

        assert len(sections) == 2
        assert sections[0]["title"] == "History"
        assert sections[1]["title"] == "Features"

    def test_get_sections_not_found(self):
        """Test sections retrieval for non-existent page."""
        mock_page = Mock()
        mock_page.exists.return_value = False

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            sections = self.client.get_sections("NonExistentPage")

        assert sections == []

    def test_get_links_success(self):
        """Test successful links retrieval."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.links = {"Link1": None, "Link2": None, "Link3": None}

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            links = self.client.get_links("Test Page")

        assert len(links) == 3
        assert "Link1" in links
        assert "Link2" in links
        assert "Link3" in links

    def test_get_links_not_found(self):
        """Test links retrieval for non-existent page."""
        mock_page = Mock()
        mock_page.exists.return_value = False

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            links = self.client.get_links("NonExistentPage")

        assert links == []

    def test_get_related_topics_success(self):
        """Test successful related topics retrieval."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.links = {"Related Link 1": None, "Related Link 2": None}
        mock_page.categories = {"Category:Test Category": None}

        # Mock related page
        mock_related_page = Mock()
        mock_related_page.exists.return_value = True
        mock_related_page.summary = "Summary of related page"
        mock_related_page.fullurl = "https://en.wikipedia.org/wiki/Related_Link_1"

        with patch.object(self.client.wiki, "page") as mock_wiki_page:
            mock_wiki_page.side_effect = lambda title: (
                mock_related_page if title in ["Related Link 1", "Related Link 2"] else mock_page
            )

            related = self.client.get_related_topics("Test Page", limit=3)

        assert len(related) >= 1
        assert any(topic["type"] == "link" for topic in related)

    def test_get_related_topics_not_found(self):
        """Test related topics retrieval for non-existent page."""
        mock_page = Mock()
        mock_page.exists.return_value = False

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            related = self.client.get_related_topics("NonExistentPage")

        assert related == []

    def test_extract_sections(self):
        """Test section extraction helper method."""
        # Mock section structure
        mock_subsection = Mock()
        mock_subsection.title = "Subsection"
        mock_subsection.text = "Subsection text"
        mock_subsection.sections = []

        mock_section = Mock()
        mock_section.title = "Main Section"
        mock_section.text = "Main section text"
        mock_section.sections = [mock_subsection]

        sections = self.client._extract_sections([mock_section])

        assert len(sections) == 1
        assert sections[0]["title"] == "Main Section"
        assert sections[0]["level"] == 0
        assert sections[0]["text"] == "Main section text"
        assert len(sections[0]["sections"]) == 1
        assert sections[0]["sections"][0]["title"] == "Subsection"
        assert sections[0]["sections"][0]["level"] == 1

    def test_summarize_for_query_success(self):
        """Test successful query-focused summary retrieval."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.text = (
            "This is a long text about a specific keyword. " "We want to find this keyword and summarize around it."
        )
        mock_page.summary = "This is a general summary."

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_for_query("Test Page", "keyword", max_length=50)

        assert "keyword" in summary
        assert len(summary) <= 50 + 3  # for "..."

    def test_summarize_for_query_not_found(self):
        """Test query-focused summary when query is not in text."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.text = "This is some other text."
        mock_page.summary = "A general summary content."

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_for_query("Test Page", "missing_keyword", max_length=30)

        assert "A general summary content."[:30] in summary  # Should return start of summary

    def test_summarize_for_query_page_not_exists(self):
        """Test query-focused summary when page does not exist."""
        mock_page = Mock()
        mock_page.exists.return_value = False
        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_for_query("NonExistent Page", "keyword")
        assert "No Wikipedia article found for 'NonExistent Page'" in summary

    def test_summarize_section_success(self):
        """Test successful section summary retrieval."""
        mock_section_target = Mock()
        mock_section_target.title = "Target Section"
        mock_section_target.text = "This is the text of the target section. It is fairly long."
        mock_section_target.sections = []

        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.sections = [mock_section_target]

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_section("Test Page", "Target Section", max_length=20)

        assert summary == "This is the text of ..."
        assert len(summary) <= 20 + 3

    def test_summarize_section_not_found(self):
        """Test section summary when section does not exist."""
        mock_other_section = Mock()
        mock_other_section.title = "Other Section"
        mock_other_section.text = "Some text."
        mock_other_section.sections = []

        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.sections = [mock_other_section]

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_section("Test Page", "Missing Section")
        assert "Section 'Missing Section' not found or is empty" in summary

    def test_summarize_section_page_not_exists(self):
        """Test section summary when page does not exist."""
        mock_page = Mock()
        mock_page.exists.return_value = False
        with patch.object(self.client.wiki, "page", return_value=mock_page):
            summary = self.client.summarize_section("NonExistent Page", "Any Section")
        assert "No Wikipedia article found for 'NonExistent Page'" in summary

    def test_extract_facts_success_from_summary(self):
        """Test successful fact extraction from page summary."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.summary = "Fact one. Fact two. Fact three. Fact four. Fact five. Fact six."
        mock_page.sections = []

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            facts = self.client.extract_facts("Test Page", count=3)

        assert len(facts) == 3
        assert facts[0] == "Fact one."
        assert facts[1] == "Fact two."
        assert facts[2] == "Fact three."

    def test_extract_facts_success_from_section(self):
        """Test successful fact extraction from a specific section."""
        mock_target_section = Mock()
        mock_target_section.title = "Key Info"
        mock_target_section.text = "Important fact A. Important fact B. Important fact C."
        mock_target_section.sections = []

        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.summary = "General summary."
        mock_page.sections = [mock_target_section]

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            facts = self.client.extract_facts("Test Page", topic_within_article="Key Info", count=2)

        assert len(facts) == 2
        assert facts[0] == "Important fact A."
        assert facts[1] == "Important fact B."

    def test_extract_facts_section_not_found_fallback_to_summary(self):
        """Test fact extraction falls back to summary if section not found."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Test Page"
        mock_page.summary = "Summary fact 1. Summary fact 2."
        mock_page.sections = []  # No sections defined

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            facts = self.client.extract_facts("Test Page", topic_within_article="Missing Section", count=1)

        assert len(facts) == 1
        assert facts[0] == "Summary fact 1."

    def test_extract_facts_page_not_exists(self):
        """Test fact extraction when page does not exist."""
        mock_page = Mock()
        mock_page.exists.return_value = False
        with patch.object(self.client.wiki, "page", return_value=mock_page):
            facts = self.client.extract_facts("NonExistent Page")
        assert len(facts) == 1
        assert "No Wikipedia article found for 'NonExistent Page'" in facts[0]

    def test_extract_facts_no_content(self):
        """Test fact extraction when there is no summary or section text."""
        mock_page = Mock()
        mock_page.exists.return_value = True
        mock_page.title = "Empty Page"
        mock_page.summary = ""  # Empty summary
        mock_page.sections = []

        with patch.object(self.client.wiki, "page", return_value=mock_page):
            facts = self.client.extract_facts("Empty Page")
        assert len(facts) == 1
        assert "No content found to extract facts from." in facts[0]


class TestMCPServerTools:
    """Test the MCP server tools."""

    def setup_method(self):
        """Set up test fixtures."""
        self.server = create_server()

    @patch("wikipedia_mcp.server.WikipediaClient")
    def test_create_server_with_language(self, MockWikipediaClient):
        """Test that create_server initializes WikipediaClient with the specified language."""
        create_server(language="ja")
        MockWikipediaClient.assert_called_once_with(language="ja", country=None, enable_cache=False, access_token=None)

    @patch("wikipedia_mcp.server.WikipediaClient")
    def test_create_server_default_language(self, MockWikipediaClient):
        """Test that create_server uses 'en' if no language is specified."""
        create_server()
        MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=False, access_token=None)

    @patch("wikipedia_mcp.server.WikipediaClient")
    def test_create_server_with_cache_enabled(self, MockWikipediaClient):
        """Test that create_server initializes WikipediaClient with caching enabled."""
        create_server(language="en", enable_cache=True)
        MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=True, access_token=None)

    @patch("wikipedia_mcp.server.WikipediaClient")
    def test_create_server_with_cache_disabled(self, MockWikipediaClient):
        """Test that create_server initializes WikipediaClient with caching disabled by default."""
        create_server(language="en", enable_cache=False)
        MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=False, access_token=None)

    def test_search_wikipedia_tool(self):
        """Test search_wikipedia tool registration."""
        # Test that server can be created without errors
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_get_article_tool(self):
        """Test get_article tool registration."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_get_summary_tool(self):
        """Test get_summary tool registration."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_get_sections_tool(self):
        """Test get_sections tool registration."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_get_links_tool(self):
        """Test get_links tool registration."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_get_related_topics_tool(self):
        """Test get_related_topics tool registration."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_connectivity_tool(self):
        """Ensure the connectivity test tool is registered."""
        server = create_server()
        assert server is not None

        async def gather_tools():
            tools = await get_tools(server)
            return list(tools.keys())

        tool_names = asyncio.run(gather_tools())
        assert "test_wikipedia_connectivity" in tool_names


class TestIntegration:
    """Integration tests for the complete system."""

    def test_server_creation(self):
        """Test that server can be created without errors."""
        server = create_server()
        assert server is not None
        assert server.name == "Wikipedia"

    def test_client_initialization(self):
        """Test that client can be initialized with different languages."""
        client_en = WikipediaClient("en")
        assert client_en.original_language == "en"
        assert client_en.base_language == "en"
        assert client_en.language_variant is None

        client_es = WikipediaClient("es")
        assert client_es.original_language == "es"
        assert client_es.base_language == "es"
        assert client_es.language_variant is None

    @pytest.mark.integration
    def test_real_wikipedia_search(self):
        """Integration test with real Wikipedia API (marked as integration)."""
        client = WikipediaClient()
        results = client.search("Python programming", limit=1)

        # This test requires internet connection
        if results:  # Only assert if we got results (network dependent)
            assert len(results) >= 1
            assert "title" in results[0]
            assert "snippet" in results[0]

    @pytest.mark.integration
    def test_real_wikipedia_summary(self):
        """Integration test for real summary retrieval."""
        client = WikipediaClient()
        summary = client.get_summary("Python (programming language)")

        # This test requires internet connection
        if "Error" not in summary and "No Wikipedia article found" not in summary:
            assert len(summary) > 0
            assert isinstance(summary, str)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
