# -*- coding: utf-8 -*-
"""
_consume_stream 视觉规则测试：
- 第一 content chunk 前输出前分隔线 + 青色开始码
- 纯 tool_calls（无 content）不输出分隔线
- content 后跟 tool_calls 时仍正常输出前分隔线 + 青色

注：后分隔线 + 关青色由主循环在 _consume_stream 返回后处理，
不在 _consume_stream 内部，本测试不覆盖。
"""
from unittest.mock import MagicMock, patch

import click
import pytest

from abyss.main import _consume_stream


@pytest.fixture(autouse=True)
def force_color(monkeypatch):
    """click.echo 在非 TTY 环境会剥离 ANSI 颜色码，测试时强制 color=True 保留。"""
    real_echo = click.echo

    def echo_keep_ansi(message="", **kwargs):
        kwargs.pop("color", None)
        return real_echo(message, color=True, **kwargs)

    monkeypatch.setattr(click, "echo", echo_keep_ansi)


def _make_chunk(content=None, reasoning=None, tool_calls=None, usage=None):
    """构造一个 chunk dict。"""
    chunk = {}
    if content is not None:
        chunk["content"] = content
    if reasoning is not None:
        chunk["reasoning_content"] = reasoning
    if tool_calls is not None:
        chunk["tool_calls"] = tool_calls
    if usage is not None:
        chunk["usage"] = usage
    return chunk


@pytest.fixture
def mock_spinner():
    s = MagicMock()
    s.stop = MagicMock()
    return s


@pytest.fixture
def patched_globals():
    """mock main 模块的全局 _ESC 和 logger，避免实际监听/写日志。"""
    with patch("abyss.main._ESC", None), \
         patch("abyss.main.logger") as mock_logger:
        mock_logger.api_first_token = MagicMock()
        mock_logger.stream_reasoning_start = MagicMock()
        mock_logger.stream_content_start = MagicMock()
        yield mock_logger


class TestContentLeadingSeparator:
    def test_first_content_emits_separator_and_cyan(
        self, capsys, mock_spinner, patched_globals
    ):
        """第一 content chunk 前应有灰色分隔线和 `\x1b[36m` 青色开始码。"""
        chunks = [_make_chunk(content="hello")]
        content, reasoning, tool_calls, usage = _consume_stream(
            chunks, mock_spinner, show_reasoning=False
        )
        out = capsys.readouterr().out
        # 灰色分隔线（含 ─ 字符）
        assert "\x1b[90m" in out
        assert "─" in out
        # 青色开始码
        assert "\x1b[36m" in out
        # 实际内容
        assert "hello" in out
        assert content == "hello"

    def test_multiple_content_chunks_only_one_separator(
        self, capsys, mock_spinner, patched_globals
    ):
        """多个 content chunk 只应在第一 chunk 前输出一次分隔线。"""
        chunks = [
            _make_chunk(content="A"),
            _make_chunk(content="B"),
            _make_chunk(content="C"),
        ]
        _consume_stream(chunks, mock_spinner, show_reasoning=False)
        out = capsys.readouterr().out
        # ─ 字符应集中在前分隔线（一段连续的 ─），不应散落多处
        # 计算分隔线出现次数：\x1b[90m 开头的整段
        sep_count = out.count("\x1b[90m")
        assert sep_count == 1, f"应只有 1 条前分隔线，实际 {sep_count}"

    def test_content_chunks_concatenate_within_cyan(
        self, capsys, mock_spinner, patched_globals
    ):
        """多个 content chunk 应都流式输出，且都在青色范围内。"""
        chunks = [
            _make_chunk(content="Hello"),
            _make_chunk(content=" "),
            _make_chunk(content="World"),
        ]
        _consume_stream(chunks, mock_spinner, show_reasoning=False)
        out = capsys.readouterr().out
        # 内容应拼接为 "Hello World"
        assert "Hello World" in out
        # 青色码应只出现一次（开始）
        assert out.count("\x1b[36m") == 1


class TestToolCallsNoSeparator:
    def test_pure_tool_calls_no_separator(self, capsys, mock_spinner, patched_globals):
        """纯 tool_calls（无 content）不应输出分隔线或青色码。"""
        chunks = [_make_chunk(tool_calls=[
            {"id": "1", "function": {"name": "read_file", "arguments": "{}"}}
        ])]
        content, reasoning, tool_calls, usage = _consume_stream(
            chunks, mock_spinner, show_reasoning=False
        )
        out = capsys.readouterr().out
        # 不应有分隔线
        assert "─" not in out
        # 不应有青色码
        assert "\x1b[36m" not in out
        # content 应为空
        assert content == ""
        # tool_calls 应被收集
        assert len(tool_calls) == 1

    def test_content_then_tool_calls_emits_separator(
        self, capsys, mock_spinner, patched_globals
    ):
        """content + tool_calls 场景：content 部分应有前分隔线 + 青色，
        tool_calls 部分不加分隔线。"""
        chunks = [
            _make_chunk(content="thinking..."),
            _make_chunk(tool_calls=[
                {"id": "1", "function": {"name": "search", "arguments": "{}"}}
            ]),
        ]
        content, reasoning, tool_calls, usage = _consume_stream(
            chunks, mock_spinner, show_reasoning=False
        )
        out = capsys.readouterr().out
        # 应有前分隔线（content 触发）
        assert "\x1b[90m" in out
        assert "─" in out
        # 应有青色码
        assert "\x1b[36m" in out
        # content 和 tool_calls 都被收集
        assert content == "thinking..."
        assert len(tool_calls) == 1


class TestEmptyStream:
    def test_empty_chunks_no_output(self, capsys, mock_spinner, patched_globals):
        """空 chunks 流不应输出任何内容。"""
        chunks = []
        content, reasoning, tool_calls, usage = _consume_stream(
            chunks, mock_spinner, show_reasoning=False
        )
        out = capsys.readouterr().out
        assert out == ""
        assert content == ""
        assert tool_calls == []

    def test_empty_content_chunk_no_separator(self, capsys, mock_spinner, patched_globals):
        """content="" 的 chunk（空字符串）不应触发分隔线（chunk.get("content") 为假值）。"""
        chunks = [_make_chunk(content="")]
        content, reasoning, tool_calls, usage = _consume_stream(
            chunks, mock_spinner, show_reasoning=False
        )
        out = capsys.readouterr().out
        # 不应有分隔线
        assert "─" not in out
        assert content == ""
