"""GLM MCP Server 테스트 스위트.

헤임달(테스터) 역할로 작성된 테스트입니다.
z.ai API 잔액 부족(429) 상황을 고려하여 전부 mock으로 처리합니다.
"""

import importlib
import sys
import types
from pathlib import Path
from unittest.mock import MagicMock, call, patch

import pytest

# server.py가 있는 디렉토리를 sys.path에 추가
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def mock_api_response():
    """단일 성공 API 응답 mock을 반환하는 fixture."""
    response = MagicMock()
    response.choices[0].message.content = "mock response text"
    return response


@pytest.fixture()
def mock_client(mock_api_response):
    """client.chat.completions.create를 mock한 fixture."""
    with patch("server.client") as patched_client:
        patched_client.chat.completions.create.return_value = mock_api_response
        yield patched_client


# ---------------------------------------------------------------------------
# 1. SYSTEM_PROMPTS 정의 확인
# ---------------------------------------------------------------------------


class TestSystemPromptsDefinition:
    def test_system_prompts_defined(self):
        """SYSTEM_PROMPTS에 4개 역할이 모두 정의되었는지 확인."""
        # Arrange
        import server

        required_roles = {"backend", "frontend", "uxui", "tester"}

        # Act
        defined_roles = set(server.SYSTEM_PROMPTS.keys())

        # Assert
        assert required_roles == defined_roles, (
            f"누락된 역할: {required_roles - defined_roles}"
        )

    def test_system_prompts_not_empty(self):
        """각 역할의 system prompt가 빈 문자열이 아닌지 확인."""
        import server

        for role, prompt in server.SYSTEM_PROMPTS.items():
            assert isinstance(prompt, str), f"{role} prompt는 str이어야 합니다"
            assert len(prompt.strip()) > 0, f"{role} prompt가 비어 있습니다"


# ---------------------------------------------------------------------------
# 2. glm_generate — 기본 API 호출
# ---------------------------------------------------------------------------


class TestGlmGenerateBasic:
    def test_glm_generate_basic(self, mock_client):
        """glm_generate가 올바른 인자로 API를 호출하는지 확인."""
        import server

        # Arrange
        prompt = "Hello, GLM!"
        model = "glm-4.7-flash"

        # Act
        result = server.glm_generate(prompt=prompt, model=model)

        # Assert
        mock_client.chat.completions.create.assert_called_once()
        call_kwargs = mock_client.chat.completions.create.call_args
        assert call_kwargs.kwargs["model"] == model
        messages = call_kwargs.kwargs["messages"]
        assert any(m["role"] == "user" and m["content"] == prompt for m in messages)
        assert result == "mock response text"

    def test_glm_generate_returns_string(self, mock_client):
        """glm_generate가 문자열을 반환하는지 확인."""
        import server

        result = server.glm_generate(prompt="test")

        assert isinstance(result, str)

    def test_glm_generate_default_model(self, mock_client):
        """glm_generate의 기본 모델이 glm-4.7-flash인지 확인."""
        import server

        server.glm_generate(prompt="test")

        call_kwargs = mock_client.chat.completions.create.call_args
        assert call_kwargs.kwargs["model"] == "glm-4.7-flash"


# ---------------------------------------------------------------------------
# 3. glm_generate — system_prompt 포함 시 messages 구성 확인
# ---------------------------------------------------------------------------


class TestGlmGenerateWithSystemPrompt:
    def test_glm_generate_with_system_prompt(self, mock_client):
        """system_prompt 지정 시 messages에 system 역할 메시지가 추가되는지 확인."""
        import server

        # Arrange
        system_prompt = "당신은 전문가입니다."
        prompt = "FastAPI 예제 작성"

        # Act
        server.glm_generate(prompt=prompt, system_prompt=system_prompt)

        # Assert
        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_messages = [m for m in messages if m["role"] == "system"]
        assert len(system_messages) == 1
        assert system_messages[0]["content"] == system_prompt

    def test_glm_generate_without_system_prompt_no_system_message(self, mock_client):
        """system_prompt 미지정 시 system 역할 메시지가 없는지 확인."""
        import server

        # Act
        server.glm_generate(prompt="test", system_prompt="")

        # Assert
        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_messages = [m for m in messages if m["role"] == "system"]
        assert len(system_messages) == 0

    def test_glm_generate_message_order(self, mock_client):
        """system 메시지가 user 메시지보다 앞에 위치하는지 확인."""
        import server

        server.glm_generate(prompt="test", system_prompt="system content")

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        roles = [m["role"] for m in messages]
        assert roles.index("system") < roles.index("user")


# ---------------------------------------------------------------------------
# 4. glm_code — 리뷰 사이클 수행 확인
# ---------------------------------------------------------------------------


class TestGlmCodeWithReview:
    def test_glm_code_with_review(self, mock_client):
        """glm_code가 1회 리뷰 사이클 시 총 3번 API를 호출하는지 확인.

        호출 순서: 초기 코드 생성 → 리뷰 → 수정
        """
        import server

        # Arrange
        mock_client.chat.completions.create.side_effect = [
            _make_response("initial code"),
            _make_response("review feedback"),
            _make_response("fixed code"),
        ]

        # Act
        result = server.glm_code(task="유틸 함수 작성", review_cycles=1)

        # Assert
        assert mock_client.chat.completions.create.call_count == 3
        assert result == "fixed code"

    def test_glm_code_zero_review_cycles(self, mock_client):
        """review_cycles=0 시 API가 1번만 호출(초기 생성만)되는지 확인."""
        import server

        # Act
        server.glm_code(task="간단한 함수", review_cycles=0)

        # Assert
        assert mock_client.chat.completions.create.call_count == 1

    def test_glm_code_review_messages_include_code(self, mock_client):
        """리뷰 메시지에 생성된 코드가 포함되는지 확인."""
        import server

        mock_client.chat.completions.create.side_effect = [
            _make_response("generated code content"),
            _make_response("review feedback"),
            _make_response("fixed code"),
        ]

        server.glm_code(task="작업", review_cycles=1)

        # 두 번째 호출(리뷰 요청)의 user 메시지에 코드가 포함되어야 함
        review_call = mock_client.chat.completions.create.call_args_list[1]
        review_messages = review_call.kwargs["messages"]
        user_messages = [m for m in review_messages if m["role"] == "user"]
        assert any("generated code content" in m["content"] for m in user_messages)

    def test_glm_code_fix_messages_include_feedback(self, mock_client):
        """코드 수정 메시지에 리뷰 피드백이 포함되는지 확인."""
        import server

        mock_client.chat.completions.create.side_effect = [
            _make_response("initial code"),
            _make_response("critical review feedback"),
            _make_response("fixed code"),
        ]

        server.glm_code(task="작업", review_cycles=1)

        # 세 번째 호출(코드 수정)의 user 메시지에 피드백이 포함되어야 함
        fix_call = mock_client.chat.completions.create.call_args_list[2]
        fix_messages = fix_call.kwargs["messages"]
        user_messages = [m for m in fix_messages if m["role"] == "user"]
        assert any("critical review feedback" in m["content"] for m in user_messages)


# ---------------------------------------------------------------------------
# 5. glm_code — review_cycles 클램핑 확인
# ---------------------------------------------------------------------------


class TestGlmCodeReviewCyclesClamped:
    def test_review_cycles_clamped_below_zero(self, mock_client):
        """review_cycles가 음수이면 0으로 클램핑되어 API가 1번만 호출되는지 확인."""
        import server

        server.glm_code(task="작업", review_cycles=-5)

        assert mock_client.chat.completions.create.call_count == 1

    def test_review_cycles_clamped_above_three(self, mock_client):
        """review_cycles가 3을 초과하면 3으로 클램핑되는지 확인.

        API 호출 횟수: 초기(1) + 3사이클 × 2(리뷰+수정) = 7
        """
        import server

        mock_client.chat.completions.create.side_effect = [
            _make_response(f"response_{i}") for i in range(7)
        ]

        server.glm_code(task="작업", review_cycles=100)

        assert mock_client.chat.completions.create.call_count == 7

    def test_review_cycles_at_boundary_zero(self, mock_client):
        """review_cycles=0 경계값: API 1회 호출."""
        import server

        server.glm_code(task="작업", review_cycles=0)

        assert mock_client.chat.completions.create.call_count == 1

    def test_review_cycles_at_boundary_three(self, mock_client):
        """review_cycles=3 경계값: API 7회 호출."""
        import server

        mock_client.chat.completions.create.side_effect = [
            _make_response(f"response_{i}") for i in range(7)
        ]

        server.glm_code(task="작업", review_cycles=3)

        assert mock_client.chat.completions.create.call_count == 7


# ---------------------------------------------------------------------------
# 6. glm_backend — 모델 확인
# ---------------------------------------------------------------------------


class TestGlmBackendModel:
    def test_glm_backend_uses_correct_model(self, mock_client):
        """glm_backend가 glm-5 모델을 사용하는지 확인."""
        import server

        # Act
        server.glm_backend(task="DB 설계")

        # Assert
        call_kwargs = mock_client.chat.completions.create.call_args
        assert call_kwargs.kwargs["model"] == "glm-5"


# ---------------------------------------------------------------------------
# 7. glm_frontend — 모델 확인
# ---------------------------------------------------------------------------


class TestGlmFrontendModel:
    def test_glm_frontend_uses_correct_model(self, mock_client):
        """glm_frontend가 glm-4.7-flash 모델을 사용하는지 확인."""
        import server

        # Act
        server.glm_frontend(task="React 컴포넌트 작성")

        # Assert
        call_kwargs = mock_client.chat.completions.create.call_args
        assert call_kwargs.kwargs["model"] == "glm-5"


# ---------------------------------------------------------------------------
# 8. 역할별 tool — 올바른 system prompt 사용 확인
# ---------------------------------------------------------------------------


class TestRoleToolsUseCorrectPrompts:
    def test_glm_backend_uses_backend_prompt(self, mock_client):
        """glm_backend가 SYSTEM_PROMPTS['backend']를 사용하는지 확인."""
        import server

        server.glm_backend(task="작업")

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_content = next(
            m["content"] for m in messages if m["role"] == "system"
        )
        assert system_content == server.SYSTEM_PROMPTS["backend"]

    def test_glm_frontend_uses_frontend_prompt(self, mock_client):
        """glm_frontend가 SYSTEM_PROMPTS['frontend']를 사용하는지 확인."""
        import server

        server.glm_frontend(task="작업")

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_content = next(
            m["content"] for m in messages if m["role"] == "system"
        )
        assert system_content == server.SYSTEM_PROMPTS["frontend"]

    def test_glm_uxui_uses_uxui_prompt(self, mock_client):
        """glm_uxui가 SYSTEM_PROMPTS['uxui']를 사용하는지 확인."""
        import server

        server.glm_uxui(task="작업")

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_content = next(
            m["content"] for m in messages if m["role"] == "system"
        )
        assert system_content == server.SYSTEM_PROMPTS["uxui"]

    def test_glm_tester_uses_tester_prompt(self, mock_client):
        """glm_tester가 SYSTEM_PROMPTS['tester']를 사용하는지 확인."""
        import server

        server.glm_tester(task="작업")

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_content = next(
            m["content"] for m in messages if m["role"] == "system"
        )
        assert system_content == server.SYSTEM_PROMPTS["tester"]

    def test_glm_code_default_role_uses_backend_prompt(self, mock_client):
        """glm_code의 기본 role(backend)이 backend system prompt를 사용하는지 확인."""
        import server

        server.glm_code(task="작업", review_cycles=0)

        call_kwargs = mock_client.chat.completions.create.call_args
        messages = call_kwargs.kwargs["messages"]
        system_content = next(
            m["content"] for m in messages if m["role"] == "system"
        )
        assert system_content == server.SYSTEM_PROMPTS["backend"]


# ---------------------------------------------------------------------------
# 9. API 실패 시 재시도 동작 확인
# ---------------------------------------------------------------------------


class TestApiRetryOnFailure:
    def test_api_retry_on_failure(self):
        """API 실패 시 MAX_RETRIES 횟수만큼 재시도하는지 확인."""
        import server

        # Arrange: 처음 두 번 실패 후 세 번째 성공
        with patch("server.client") as mock_client, patch(
            "server.time.sleep"
        ) as mock_sleep:
            mock_client.chat.completions.create.side_effect = [
                Exception("첫 번째 실패"),
                Exception("두 번째 실패"),
                _make_response("최종 성공"),
            ]

            # Act
            result = server._call_api(
                messages=[{"role": "user", "content": "test"}],
                model="glm-4.7-flash",
                max_tokens=100,
            )

        # Assert
        assert result == "최종 성공"
        assert mock_client.chat.completions.create.call_count == 3
        # MAX_RETRIES=2이므로 sleep은 2번 호출(attempt 0, 1 실패 후)
        assert mock_sleep.call_count == 2

    def test_api_raises_runtime_error_after_all_retries_exhausted(self):
        """모든 재시도 실패 시 RuntimeError가 발생하는지 확인."""
        import server

        with patch("server.client") as mock_client, patch("server.time.sleep"):
            mock_client.chat.completions.create.side_effect = Exception("항상 실패")

            with pytest.raises(RuntimeError, match="API 호출 실패"):
                server._call_api(
                    messages=[{"role": "user", "content": "test"}],
                    model="glm-4.7-flash",
                    max_tokens=100,
                )

    def test_api_retry_total_attempts_equals_max_retries_plus_one(self):
        """총 시도 횟수가 MAX_RETRIES + 1인지 확인."""
        import server

        with patch("server.client") as mock_client, patch("server.time.sleep"):
            mock_client.chat.completions.create.side_effect = Exception("항상 실패")

            with pytest.raises(RuntimeError):
                server._call_api(
                    messages=[{"role": "user", "content": "test"}],
                    model="glm-4.7-flash",
                    max_tokens=100,
                )

        expected_calls = server.MAX_RETRIES + 1
        assert mock_client.chat.completions.create.call_count == expected_calls

    def test_glm_generate_returns_error_string_on_api_failure(self):
        """glm_generate가 API 전체 실패 시 [오류] 접두어 문자열을 반환하는지 확인."""
        import server

        with patch("server.client") as mock_client, patch("server.time.sleep"):
            mock_client.chat.completions.create.side_effect = Exception("실패")

            result = server.glm_generate(prompt="test")

        assert result.startswith("[오류]")

    def test_api_sleep_interval_is_retry_interval(self):
        """재시도 간 sleep이 RETRY_INTERVAL 값으로 호출되는지 확인."""
        import server

        with patch("server.client") as mock_client, patch(
            "server.time.sleep"
        ) as mock_sleep:
            mock_client.chat.completions.create.side_effect = [
                Exception("실패"),
                _make_response("성공"),
            ]

            server._call_api(
                messages=[{"role": "user", "content": "test"}],
                model="glm-4.7-flash",
                max_tokens=100,
            )

        mock_sleep.assert_called_with(server.RETRY_INTERVAL)


# ---------------------------------------------------------------------------
# 10. MCP tool 등록 확인
# ---------------------------------------------------------------------------


class TestMcpToolsRegistered:
    def test_mcp_tools_registered(self):
        """6개 tool이 모두 MCP에 등록되었는지 확인."""
        import server

        # Arrange
        expected_tools = {
            "glm_generate",
            "glm_code",
            "glm_backend",
            "glm_frontend",
            "glm_uxui",
            "glm_tester",
        }

        # Act: FastMCP 내부 _tools 딕셔너리로 등록 여부 확인
        registered = set(server.mcp._tool_manager._tools.keys())

        # Assert
        missing = expected_tools - registered
        assert not missing, f"등록되지 않은 tool: {missing}"

    def test_mcp_tools_count(self):
        """MCP에 등록된 tool 수가 정확히 6개인지 확인."""
        import server

        registered = server.mcp._tool_manager._tools
        assert len(registered) == 6


# ---------------------------------------------------------------------------
# 헬퍼 함수
# ---------------------------------------------------------------------------


def _make_response(content: str) -> MagicMock:
    """지정된 content를 가진 API 응답 mock 객체를 생성합니다."""
    response = MagicMock()
    response.choices[0].message.content = content
    return response
