"""
test_mcp_server.py

TDD RED 단계: mcp_server.py 구현 전 테스트 먼저 작성.

테스트 대상:
- search_knowledge tool: hybrid_search() 호출
- get_document tool: Supabase knowledge_documents 테이블 조회
- list_sources tool: source별 GROUP BY COUNT

테스트 전략:
- pytest.mark.anyio: FastMCP가 async 기반
- unittest.mock.patch: 외부 의존성(hybrid_search, Supabase) mock
- mcp.call_tool(name, arguments): MCP 프로토콜 레벨 tool 호출
"""

import json
import sys
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

# mcp_server.py에서 mcp 인스턴스 import
# (mcp_server.py가 없으므로 RED 단계에서 ImportError 발생)
from mcp_server import mcp as server  # type: ignore[import-not-found]

# ---------------------------------------------------------------------------
# search_knowledge 테스트
# ---------------------------------------------------------------------------


@pytest.mark.anyio
async def test_search_knowledge_basic():
    """기본 검색: query만 제공하면 hybrid_search가 호출되고 결과가 반환된다."""
    mock_search_results = [
        {
            "content": "보험 약관 내용입니다.",
            "title": "보험 약관 제1조",
            "source": "insurance_docs",
            "similarity": 0.95,
            "combined_score": 0.92,
            "document_id": "doc-001",
        },
        {
            "content": "보험료 납입에 관한 내용입니다.",
            "title": "보험료 납입",
            "source": "insurance_docs",
            "similarity": 0.87,
            "combined_score": 0.85,
            "document_id": "doc-002",
        },
    ]

    with patch("mcp_server.hybrid_search", return_value=mock_search_results) as mock_hs:
        result = await server.call_tool("search_knowledge", {"query": "보험 약관"})

        # call_tool은 (content_blocks, structured_result) 튜플 반환
        content_blocks, structured = result

        # hybrid_search가 올바른 인자로 호출되었는지 확인
        mock_hs.assert_called_once_with(
            query="보험 약관",
            limit=5,
            source_filter=None,
        )

        # 반환된 content_blocks가 있어야 함
        assert len(content_blocks) > 0

        # structured result에 결과 목록이 있어야 함
        result_list = structured.get("result", [])
        assert len(result_list) == 2

        # 각 항목에 필수 필드가 있어야 함
        first_item = result_list[0]
        assert "content" in first_item
        assert "title" in first_item
        assert "source" in first_item
        assert "similarity" in first_item

        assert first_item["content"] == "보험 약관 내용입니다."
        assert first_item["title"] == "보험 약관 제1조"
        assert first_item["source"] == "insurance_docs"
        assert first_item["similarity"] == 0.95


@pytest.mark.anyio
async def test_search_knowledge_with_source_filter():
    """source 필터: source 파라미터가 hybrid_search의 source_filter로 전달된다."""
    mock_search_results = [
        {
            "content": "필터된 결과",
            "title": "필터 문서",
            "source": "specific_source",
            "similarity": 0.90,
            "combined_score": 0.88,
            "document_id": "doc-003",
        }
    ]

    with patch("mcp_server.hybrid_search", return_value=mock_search_results) as mock_hs:
        result = await server.call_tool(
            "search_knowledge",
            {"query": "검색어", "source": "specific_source"},
        )

        content_blocks, structured = result

        # source_filter가 전달되었는지 확인
        mock_hs.assert_called_once_with(
            query="검색어",
            limit=5,
            source_filter="specific_source",
        )

        result_list = structured.get("result", [])
        assert len(result_list) == 1
        assert result_list[0]["source"] == "specific_source"


@pytest.mark.anyio
async def test_search_knowledge_with_limit():
    """limit 파라미터: limit 값이 hybrid_search에 전달된다."""
    mock_search_results = []

    with patch("mcp_server.hybrid_search", return_value=mock_search_results) as mock_hs:
        result = await server.call_tool(
            "search_knowledge",
            {"query": "검색어", "limit": 10},
        )

        # limit=10이 hybrid_search로 전달되어야 함
        mock_hs.assert_called_once_with(
            query="검색어",
            limit=10,
            source_filter=None,
        )

        content_blocks, structured = result
        result_list = structured.get("result", [])
        assert result_list == []


# ---------------------------------------------------------------------------
# get_document 테스트
# ---------------------------------------------------------------------------


@pytest.mark.anyio
async def test_get_document_found():
    """문서 조회 성공: 존재하는 document_id로 조회하면 문서 상세 정보가 반환된다."""
    mock_doc = {
        "id": "doc-123",
        "title": "보험 기본 약관",
        "content": "이 약관은 보험 계약의 기본 사항을 정합니다.",
        "source": "standard_policy",
        "metadata": {"version": "2024", "category": "life_insurance"},
    }

    # Supabase 클라이언트의 응답을 mock
    mock_response = MagicMock()
    mock_response.data = [mock_doc]

    mock_supabase = MagicMock()
    mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value = mock_response

    with patch("mcp_server.supabase_client", mock_supabase):
        result = await server.call_tool("get_document", {"document_id": "doc-123"})

        content_blocks, structured = result

        doc_result = structured.get("result", {})
        assert doc_result["id"] == "doc-123"
        assert doc_result["title"] == "보험 기본 약관"
        assert doc_result["content"] == "이 약관은 보험 계약의 기본 사항을 정합니다."
        assert doc_result["source"] == "standard_policy"
        assert doc_result["metadata"] == {"version": "2024", "category": "life_insurance"}

        # Supabase 쿼리가 올바르게 호출되었는지 확인
        mock_supabase.table.assert_called_with("knowledge_documents")


@pytest.mark.anyio
async def test_get_document_not_found():
    """문서 조회 실패: 존재하지 않는 document_id로 조회하면 None 또는 에러 반환."""
    mock_response = MagicMock()
    mock_response.data = []  # 결과 없음

    mock_supabase = MagicMock()
    mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value = mock_response

    with patch("mcp_server.supabase_client", mock_supabase):
        result = await server.call_tool("get_document", {"document_id": "nonexistent-doc"})

        content_blocks, structured = result

        # 존재하지 않을 때 None 또는 에러 메시지 반환
        doc_result = structured.get("result", None)
        # None이거나 에러 메시지가 있어야 함
        assert doc_result is None or "error" in str(doc_result).lower() or "not found" in str(doc_result).lower()


# ---------------------------------------------------------------------------
# list_sources 테스트
# ---------------------------------------------------------------------------


@pytest.mark.anyio
async def test_list_sources():
    """소스 목록 조회: knowledge_documents에서 source별 COUNT를 반환한다."""
    mock_response = MagicMock()
    mock_response.data = [
        {"source": "insurance_docs", "document_count": 42},
        {"source": "legal_texts", "document_count": 15},
        {"source": "faq", "document_count": 8},
    ]

    mock_supabase = MagicMock()
    # list_sources는 GROUP BY 쿼리를 실행하므로 적절한 mock 설정
    mock_supabase.table.return_value.select.return_value.execute.return_value = mock_response

    with patch("mcp_server.supabase_client", mock_supabase):
        result = await server.call_tool("list_sources", {})

        content_blocks, structured = result

        sources_list = structured.get("result", [])
        assert len(sources_list) == 3

        # 각 항목에 source와 document_count 필드가 있어야 함
        first_source = sources_list[0]
        assert "source" in first_source
        assert "document_count" in first_source

        # 특정 source가 올바른 count를 가지는지 확인
        source_names = [s["source"] for s in sources_list]
        assert "insurance_docs" in source_names

        insurance_source = next(s for s in sources_list if s["source"] == "insurance_docs")
        assert insurance_source["document_count"] == 42


@pytest.mark.anyio
async def test_list_sources_empty():
    """빈 결과 조회: knowledge_documents가 비어있으면 빈 목록을 반환한다."""
    mock_response = MagicMock()
    mock_response.data = []

    mock_supabase = MagicMock()
    mock_supabase.table.return_value.select.return_value.execute.return_value = mock_response

    with patch("mcp_server.supabase_client", mock_supabase):
        result = await server.call_tool("list_sources", {})

        content_blocks, structured = result

        sources_list = structured.get("result", [])
        assert sources_list == []


# ---------------------------------------------------------------------------
# 에러 핸들링 테스트
# ---------------------------------------------------------------------------


@pytest.mark.anyio
async def test_search_knowledge_error_handling():
    """에러 핸들링: hybrid_search가 예외를 던지면 적절한 에러 응답을 반환한다."""
    with patch(
        "mcp_server.hybrid_search",
        side_effect=Exception("DB connection failed"),
    ):
        # 에러가 발생해도 tool 호출이 크래시되지 않고 에러 정보를 반환해야 함
        # 또는 예외가 전파될 수 있음 - 구현에 따라 다름
        try:
            result = await server.call_tool("search_knowledge", {"query": "테스트"})
            content_blocks, structured = result

            # 에러 메시지가 포함된 응답이어야 함
            # (에러 핸들링을 구현한 경우)
            result_data = structured.get("result", None)
            # 에러 응답이거나 빈 결과여야 함
            assert result_data is not None or len(content_blocks) > 0

        except Exception as e:
            # 예외가 전파되는 경우도 허용 (구현 방식에 따라)
            assert "DB connection failed" in str(e) or "error" in str(e).lower()


# ---------------------------------------------------------------------------
# Tool 등록 테스트
# ---------------------------------------------------------------------------


@pytest.mark.anyio
async def test_tool_registration():
    """Tool 등록 확인: 3개의 tool이 모두 FastMCP 서버에 등록되어 있어야 한다."""
    tools = await server.list_tools()

    tool_names = [tool.name for tool in tools]

    # 3개의 tool이 모두 등록되어 있어야 함
    assert "search_knowledge" in tool_names, f"search_knowledge tool이 등록되지 않음. 등록된 tools: {tool_names}"
    assert "get_document" in tool_names, f"get_document tool이 등록되지 않음. 등록된 tools: {tool_names}"
    assert "list_sources" in tool_names, f"list_sources tool이 등록되지 않음. 등록된 tools: {tool_names}"

    # 정확히 3개의 tool이 등록되어야 함
    assert len(tools) == 3, f"예상 tool 수: 3, 실제: {len(tools)}. tools: {tool_names}"
