"""RAG tool error + hit paths with mocked state (no live Qdrant/Mongo)."""

import pytest

from app.services.kb.rag_tool import search_knowledge_base
from app.services.kb.schemas import KBSearchResults
from app.services.kb.state import KBRAGState, clear_state, set_state


@pytest.mark.asyncio
async def test_uninitialised_state_returns_error_status():
    clear_state()
    res = await search_knowledge_base.ainvoke({"query": "voi"})
    assert isinstance(res, KBSearchResults)
    assert res.status == "error"
    assert res.error_kind == "kb_not_initialized"


@pytest.mark.asyncio
async def test_hit_path_returns_chunks_without_parent_text():
    from langchain_core.documents import Document

    class _Graph:
        async def ainvoke(self, state):
            return {
                "docs": [
                    Document(
                        page_content="Voi is a large animal.",
                        metadata={
                            "article_id": "voi",
                            "title": "Voi",
                            "relevance_score": 0.92,
                            "language": "vi",
                            "age_min": 4,
                            "age_max": 10,
                            "tags": ["dong-vat"],
                        },
                    )
                ],
                "grade_score": 0.92,
            }

    class _DS:
        async def amget(self, ids):
            return [f"Parent body for {i}" for i in ids]

    set_state(KBRAGState(crag_graph=_Graph(), parent_docstore=_DS()))
    try:
        res = await search_knowledge_base.ainvoke({"query": "voi sống ở đâu"})
        assert res.status == "hit"
        assert len(res.chunks) == 1
        assert res.chunks[0].article_id == "voi"
        # parent_text is deprecated and always None — see plan 260610-1549.
        assert res.chunks[0].parent_text is None
        assert res.chunks[0].score == 0.92
    finally:
        clear_state()


@pytest.mark.asyncio
async def test_no_match_path_when_graph_returns_empty():
    class _Graph:
        async def ainvoke(self, state):
            return {"docs": [], "grade_score": 0.2}

    class _DS:
        async def amget(self, ids):
            return []

    set_state(KBRAGState(crag_graph=_Graph(), parent_docstore=_DS()))
    try:
        res = await search_knowledge_base.ainvoke({"query": "??"})
        assert res.status == "no_match"
        assert res.chunks == []
    finally:
        clear_state()


@pytest.mark.asyncio
async def test_graph_exception_returns_error_status_with_kind():
    class _Graph:
        async def ainvoke(self, state):
            raise RuntimeError("qdrant down")

    set_state(KBRAGState(crag_graph=_Graph(), parent_docstore=object()))
    try:
        res = await search_knowledge_base.ainvoke({"query": "x"})
        assert res.status == "error"
        assert res.error_kind == "RuntimeError"
    finally:
        clear_state()


@pytest.mark.asyncio
async def test_degraded_marker_propagates_from_chunk_metadata():
    from langchain_core.documents import Document

    class _Graph:
        async def ainvoke(self, state):
            return {
                "docs": [
                    Document(
                        page_content="x",
                        metadata={
                            "article_id": "a",
                            "title": "A",
                            "relevance_score": 0.3,
                            "kb_rerank_degraded": True,
                        },
                    )
                ],
                "grade_score": 0.3,
            }

    class _DS:
        async def amget(self, ids):
            return [None] * len(ids)

    set_state(KBRAGState(crag_graph=_Graph(), parent_docstore=_DS()))
    try:
        res = await search_knowledge_base.ainvoke({"query": "x"})
        assert res.status == "hit"
        assert res.degraded is True
    finally:
        clear_state()
