"""Integration tests for ChatPerplexity."""

import os

import pytest
from langchain_core.messages import HumanMessage

from langchain_perplexity import ChatPerplexity, MediaResponse, WebSearchOptions


@pytest.mark.skipif(not os.environ.get("PPLX_API_KEY"), reason="PPLX_API_KEY not set")
class TestChatPerplexityIntegration:
    def test_standard_generation(self) -> None:
        """Test standard generation."""
        chat = ChatPerplexity(model="sonar", temperature=0)
        message = HumanMessage(content="Hello! How are you?")
        response = chat.invoke([message])
        assert response.content
        assert isinstance(response.content, str)

    async def test_async_generation(self) -> None:
        """Test async generation."""
        chat = ChatPerplexity(model="sonar", temperature=0)
        message = HumanMessage(content="Hello! How are you?")
        response = await chat.ainvoke([message])
        assert response.content
        assert isinstance(response.content, str)

    def test_pro_search(self) -> None:
        """Test Pro Search (reasoning_steps extraction)."""
        # Pro search is available on sonar-pro
        chat = ChatPerplexity(
            model="sonar-pro",
            temperature=0,
            web_search_options=WebSearchOptions(search_type="pro"),
            streaming=True,
        )
        message = HumanMessage(content="Who won the 2024 US election and why?")

        # We need to collect chunks to check reasoning steps
        chunks = list(chat.stream([message]))
        full_content = "".join(c.content for c in chunks if isinstance(c.content, str))
        assert full_content

        # Check if any chunk has reasoning_steps
        has_reasoning = any("reasoning_steps" in c.additional_kwargs for c in chunks)
        if has_reasoning:
            assert True
        else:
            # Fallback assertion if no reasoning steps returned
            assert len(chunks) > 0

    async def test_streaming(self) -> None:
        """Test streaming."""
        chat = ChatPerplexity(model="sonar", temperature=0)
        message = HumanMessage(content="Count to 5")
        async for chunk in chat.astream([message]):
            assert isinstance(chunk.content, str)

    def test_citations_and_search_results(self) -> None:
        """Test that citations and search results are returned."""
        chat = ChatPerplexity(model="sonar", temperature=0)
        message = HumanMessage(content="Who is the CEO of OpenAI?")
        response = chat.invoke([message])

        # Citations are usually in additional_kwargs
        assert "citations" in response.additional_kwargs
        # Search results might be there too
        # Note: presence depends on whether search was performed
        if response.additional_kwargs.get("citations"):
            assert len(response.additional_kwargs["citations"]) > 0

    def test_search_control(self) -> None:
        """Test search control parameters."""
        # Test disabled search (should complete without citations)
        chat = ChatPerplexity(model="sonar", disable_search=True)
        message = HumanMessage(content="What is 2+2?")
        response = chat.invoke([message])
        assert response.content

        # Test search classifier
        chat_classifier = ChatPerplexity(model="sonar", enable_search_classifier=True)
        response_classifier = chat_classifier.invoke([message])
        assert response_classifier.content

    def test_search_recency_filter(self) -> None:
        """Test search_recency_filter parameter."""
        chat = ChatPerplexity(model="sonar", search_recency_filter="month")
        message = HumanMessage(content="Latest AI news")
        response = chat.invoke([message])
        assert response.content

    def test_search_domain_filter(self) -> None:
        """Test search_domain_filter parameter."""
        chat = ChatPerplexity(model="sonar", search_domain_filter=["wikipedia.org"])
        message = HumanMessage(content="Python programming language")
        response = chat.invoke([message])

        # Verify citations come from wikipedia if any
        if citations := response.additional_kwargs.get("citations"):
            assert any("wikipedia.org" in c for c in citations)

    def test_responses_api_with_web_search(self) -> None:
        """Hit the real Agent (Responses) API with a built-in tool."""
        # The Agent API requires a `preset` or `provider/model` format — bare
        # Chat-Completions names like `sonar-pro` are rejected. Use a preset
        # and let the `model` field get dropped by `_to_responses_payload`.
        # `temperature` is intentionally omitted: the Responses API does not
        # accept it, and supplying it would emit a per-call WARNING log.
        chat = ChatPerplexity(model="sonar-pro", use_responses_api=True)
        response = chat.invoke(
            "What is the capital of France?",
            tools=[{"type": "web_search"}],
            preset="pro-search",
        )
        assert isinstance(response.content, str)
        assert response.content
        if response.usage_metadata is not None:
            assert response.usage_metadata["input_tokens"] >= 0
            assert response.usage_metadata["output_tokens"] >= 0

    async def test_responses_api_async_with_web_search(self) -> None:
        """Hit the real Agent API asynchronously to cover `ainvoke`."""
        chat = ChatPerplexity(model="sonar-pro", use_responses_api=True)
        response = await chat.ainvoke(
            "What is the capital of France?",
            tools=[{"type": "web_search"}],
            preset="pro-search",
        )
        assert isinstance(response.content, str)
        assert response.content

    def test_responses_api_streaming_surfaces_citations(self) -> None:
        """Stream the real Agent API and verify citations surface on chunks."""
        chat = ChatPerplexity(model="sonar-pro", use_responses_api=True)
        chunks = list(
            chat.stream(
                "Who is the CEO of OpenAI?",
                tools=[{"type": "web_search"}],
                preset="pro-search",
            )
        )
        assert chunks
        full_content = "".join(c.content for c in chunks if isinstance(c.content, str))
        assert full_content
        # Citations, when returned, must land on additional_kwargs (not
        # response_metadata) to match the Chat Completions path.
        for chunk in chunks:
            assert "citations" not in chunk.response_metadata

    def test_media_and_metadata(self) -> None:
        """Test related questions and images."""
        chat = ChatPerplexity(
            model="sonar-pro",
            return_related_questions=True,
            return_images=True,
            # Media response overrides for video
            media_response=MediaResponse(overrides={"return_videos": True}),
        )
        message = HumanMessage(content="Apollo 11 moon landing")
        response = chat.invoke([message])

        # Check related questions
        if related := response.additional_kwargs.get("related_questions"):
            assert len(related) > 0

        # Check images
        if images := response.additional_kwargs.get("images"):
            assert len(images) > 0

        # Check videos (might not always be present but structure should handle it)
        if videos := response.additional_kwargs.get("videos"):
            assert len(videos) > 0
