# -*- coding: utf-8 -*-
"""
回归测试：流式响应中 chunk 间隔超时必须抛 TimeoutError，避免无限挂起。

复现：用户报告模型卡在 "STREAM reasoning_start" 后再无任何日志输出，
      日志文件最后修改时间停在 14:08:59，进程一直占用终端不退出。
根因：api_client.chat() 直接 `for chunk in response` 迭代 openai 流，
      没有 chunk 间隔超时；服务端或网络挂起时无法恢复。
"""
import time
from unittest.mock import MagicMock

import pytest

from abyss.api_client import DeepSeekClient


def _make_chunk(content=None, reasoning=None, finish=None):
    """构造一个模拟的 openai 流 chunk（与 _parse_chunk 兼容的最小结构）。"""
    delta = MagicMock()
    delta.content = content
    delta.reasoning_content = reasoning
    delta.tool_calls = None
    choice = MagicMock()
    choice.delta = delta
    choice.finish_reason = finish
    chunk = MagicMock()
    chunk.choices = [choice]
    chunk.usage = None
    return chunk


class TestStreamIdleTimeout:
    """流式响应 chunk 间隔超时检测。"""

    def test_stream_that_blocks_forever_raises_timeout(self):
        """流产出第一个 chunk 后无限阻塞 → 必须在 idle_timeout 内抛 TimeoutError。

        红：当前实现直接 `for chunk in response`，会无限挂起（测试会超时失败）。
        绿：实现后应在 stream_idle_timeout 秒内抛 TimeoutError。
        """
        # 构造阻塞流：先给一个 chunk（模拟 first_token），然后永久阻塞
        def blocking_stream():
            yield _make_chunk(reasoning="开始思考")
            # 模拟服务端挂起：永远不返回下一个 chunk
            time.sleep(30)

        client = DeepSeekClient(api_key="fake-key")
        # 替换真实 openai client 为返回阻塞流的 mock
        client.client = MagicMock()
        client.client.chat.completions.create.return_value = blocking_stream()

        # idle_timeout 设小，让测试快速失败
        chunks_iter = client.chat(
            messages=[{"role": "user", "content": "hi"}],
            stream=True,
            stream_idle_timeout=0.5,
        )

        start = time.time()
        with pytest.raises(TimeoutError):
            list(chunks_iter)
        elapsed = time.time() - start
        # 应在 ~0.5s 抛出，给 3s 余量（线程调度 + queue 开销）
        assert elapsed < 3.0, f"超时检测过慢：{elapsed:.2f}s"

    def test_normal_stream_completes_without_timeout(self):
        """正常流（chunk 间隔小于 idle_timeout）不应误报超时。"""
        def normal_stream():
            yield _make_chunk(reasoning="思考中")
            yield _make_chunk(content="回复")
            yield _make_chunk(finish="stop")

        client = DeepSeekClient(api_key="fake-key")
        client.client = MagicMock()
        client.client.chat.completions.create.return_value = normal_stream()

        chunks = list(client.chat(
            messages=[{"role": "user", "content": "hi"}],
            stream=True,
            stream_idle_timeout=5.0,
        ))
        assert len(chunks) == 3
