"""
将 messages 转为可视化 HTML 结构展示

功能：
- 每条 message 有清晰的类型标识（系统 / 用户 / 助手 / 工具）
- 工具类型标注工具名称和工具输出
- 内容过长时支持展开/收起
"""

import json
from pathlib import Path
from typing import Any, List, Union

# 展开阈值：超过此字符数则默认折叠
COLLAPSE_THRESHOLD = 300


def _ensure_messages(messages: List[Any]) -> List[dict]:
    """将 Message 对象或 dict 统一转为 dict 列表"""
    result = []
    for m in messages:
        if hasattr(m, "to_dict"):
            result.append(m.to_dict())
        elif isinstance(m, dict):
            result.append(m)
        else:
            result.append({"role": "unknown", "content": str(m)})
    return result


def _get_message_type_info(msg: dict) -> tuple[str, str, str]:
    """
    根据消息内容返回 (类型标签, 简短说明, 样式类)
    """
    role = msg.get("role", "unknown")
    content = msg.get("content")
    desc = msg.get("description", "")

    if role == "system":
        return "系统", "系统指令", "msg-system"
    if role == "user":
        return "用户", "用户输入", "msg-user"
    if role == "assistant":
        if isinstance(content, dict):
            text = content.get("text", "")
            tool_calls = content.get("tool_calls")
            if tool_calls:
                names = [
                    tc.get("function", {}).get("name", "?")
                    for tc in (tool_calls if isinstance(tool_calls, list) else [])
                ]
                label = f"工具调用: {', '.join(names)}" if names else "工具调用"
                return "助手", label, "msg-assistant-tool"
            if text:
                return "助手", "文本回复", "msg-assistant"
        return "助手", desc or "助手消息", "msg-assistant"
    if role == "tool":
        tool_name = "unknown"
        if isinstance(content, dict):
            tool_name = content.get("tool_name", content.get("name", "unknown"))
        return "工具", tool_name, "msg-tool"

    return "未知", str(role), "msg-unknown"


def _extract_display_content(msg: dict) -> str:
    """提取用于展示的文本内容"""
    role = msg.get("role", "unknown")
    content = msg.get("content")

    if role == "system" or role == "user":
        return str(content) if content else ""

    if role == "assistant" and isinstance(content, dict):
        return content.get("text", "") or ""

    if role == "tool" and isinstance(content, dict):
        result = content.get("result", content)
        if isinstance(result, list):
            return json.dumps(result, ensure_ascii=False, indent=2)
        return str(result) if result else ""

    return str(content) if content else ""


def _extract_tool_info(msg: dict) -> tuple[str, str]:
    """提取 tool 消息的工具名和输出"""
    content = msg.get("content")
    if not isinstance(content, dict):
        return "unknown", str(content or "")
    tool_name = content.get("tool_name", content.get("name", msg.get("description", "unknown")))
    result = content.get("result", content.get("output", content))
    if isinstance(result, dict) or isinstance(result, list):
        output = json.dumps(result, ensure_ascii=False, indent=2)
    else:
        output = str(result) if result is not None else ""
    return tool_name, output


def _render_collapsible(content: str, block_id: str = "") -> str:
    """生成可展开/收起的 HTML 片段"""
    content = content.strip()
    if not content:
        return '<pre class="content-body"></pre>'

    escaped = content.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    should_collapse = len(content) > COLLAPSE_THRESHOLD
    safe_id = "".join(c if c.isalnum() or c in "-_" else "-" for c in block_id) or "x"

    if should_collapse:
        preview = escaped[:COLLAPSE_THRESHOLD] + "…"
        full = escaped
        return f'''<div class="collapsible-wrap">
            <pre class="content-body content-preview" id="preview-{safe_id}">{preview}</pre>
            <pre class="content-body content-full" id="full-{safe_id}" style="display:none">{full}</pre>
            <button class="btn-toggle" onclick="toggleExpand('{safe_id}')">展开全部</button>
        </div>'''
    return f'<pre class="content-body">{escaped}</pre>'


def _render_message(msg: dict, index: int) -> str:
    """渲染单条消息为 HTML"""
    type_label, short_desc, css_class = _get_message_type_info(msg)
    seq = msg.get("sequence", index)
    role = msg.get("role", "unknown")
    bid = f"m{index}"

    # 头部：类型 + 简短说明
    header = f'<div class="msg-header"><span class="msg-type {css_class}">{type_label}</span> <span class="msg-desc">{short_desc}</span></div>'

    body_parts = []

    if role == "assistant":
        content = msg.get("content")
        if isinstance(content, dict):
            tool_calls = content.get("tool_calls")
            text = content.get("text", "")
            if tool_calls:
                for tc in tool_calls:
                    fn = tc.get("function", {})
                    name = fn.get("name", "?")
                    args_str = fn.get("arguments", "{}")
                    try:
                        args_json = json.loads(args_str)
                        args_preview = json.dumps(args_json, ensure_ascii=False)[:200]
                        if len(json.dumps(args_json)) > 200:
                            args_preview += "…"
                    except Exception:
                        args_preview = args_str[:200] + ("…" if len(args_str) > 200 else "")
                    body_parts.append(
                        f'<div class="tool-call-item"><span class="tool-name">🛠 {name}</span><pre class="tool-args">{args_preview}</pre></div>'
                    )
            if text:
                body_parts.append(_render_collapsible(text, f"{bid}-text"))

    elif role == "tool":
        tool_name, output = _extract_tool_info(msg)
        body_parts.append(f'<div class="tool-output-header"><span class="tool-name">🛠 {tool_name}</span></div>')
        body_parts.append(_render_collapsible(output, f"{bid}-tool"))

    else:
        content = _extract_display_content(msg)
        body_parts.append(_render_collapsible(content, bid))

    body = "\n".join(body_parts)
    return f'<div class="msg-item" data-role="{role}" data-seq="{seq}">{header}<div class="msg-body">{body}</div></div>'


def _build_html(messages: List[dict], title: str = "Messages") -> str:
    """构建完整 HTML 文档"""
    items_html = "\n".join(_render_message(m, i) for i, m in enumerate(messages))
    return f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<style>
* {{ box-sizing: border-box; }}
body {{ font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; line-height: 1.5; }}
h1 {{ font-size: 1.25rem; margin-bottom: 16px; color: #333; }}
.msg-list {{ display: flex; flex-direction: column; gap: 12px; }}
.msg-item {{ background: #fff; border-radius: 8px; padding: 12px 16px; box-shadow: 0 1px 3px rgba(0,0,0,.08); border-left: 4px solid #94a3b8; }}
.msg-item[data-role="system"] {{ border-left-color: #64748b; }}
.msg-item[data-role="user"] {{ border-left-color: #3b82f6; }}
.msg-item[data-role="assistant"] {{ border-left-color: #22c55e; }}
.msg-item[data-role="tool"] {{ border-left-color: #f59e0b; }}
.msg-header {{ margin-bottom: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }}
.msg-type {{ font-size: 0.75rem; font-weight: 600; padding: 2px 8px; border-radius: 4px; }}
.msg-system {{ background: #e2e8f0; color: #475569; }}
.msg-user {{ background: #dbeafe; color: #1d4ed8; }}
.msg-assistant {{ background: #dcfce7; color: #15803d; }}
.msg-assistant-tool {{ background: #fef3c7; color: #b45309; }}
.msg-tool {{ background: #fed7aa; color: #c2410c; }}
.msg-desc {{ font-size: 0.875rem; color: #64748b; }}
.msg-body {{ font-size: 0.875rem; }}
.content-body {{ margin: 0; white-space: pre-wrap; word-break: break-word; font-size: 0.8125rem; color: #334155; max-height: 400px; overflow-y: auto; }}
.tool-call-item {{ margin-bottom: 8px; }}
.tool-name {{ font-weight: 600; color: #0f172a; }}
.tool-args {{ margin: 4px 0 0 0; padding: 8px; background: #f8fafc; border-radius: 4px; font-size: 0.75rem; overflow-x: auto; }}
.tool-output-header {{ margin-bottom: 8px; }}
.btn-toggle {{ margin-top: 8px; padding: 4px 12px; font-size: 0.75rem; cursor: pointer; background: #e2e8f0; border: 1px solid #cbd5e1; border-radius: 4px; color: #475569; }}
.btn-toggle:hover {{ background: #cbd5e1; }}
.collapsible-wrap {{ position: relative; }}
</style>
</head>
<body>
<h1>{title}</h1>
<div class="msg-list">{items_html}</div>
<script>
function toggleExpand(idSuffix) {{
  var preview = document.getElementById('preview-' + idSuffix);
  var full = document.getElementById('full-' + idSuffix);
  var btn = preview.parentElement.querySelector('.btn-toggle');
  if (!preview || !full) return;
  if (full.style.display === 'none') {{
    preview.style.display = 'none';
    full.style.display = 'block';
    if (btn) btn.textContent = '收起';
  }} else {{
    preview.style.display = 'block';
    full.style.display = 'none';
    if (btn) btn.textContent = '展开全部';
  }}
}}
</script>
</body>
</html>"""


def messages_to_html(
    messages: List[Any],
    output_path: Union[str, Path],
    title: str = "Messages 可视化",
) -> Path:
    """
    将 messages 转为 HTML 并写入文件

    Args:
        messages: Message 对象或 dict 列表
        output_path: 输出 HTML 文件路径
        title: 页面标题

    Returns:
        输出文件的 Path
    """
    data = _ensure_messages(messages)
    html = _build_html(data, title)
    out = Path(output_path)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(html, encoding="utf-8")
    return out


async def trace_to_html(
    trace_id: str,
    output_path: Union[str, Path],
    base_path: str = ".trace",
    title: str | None = None,
) -> Path:
    """
    从 Trace 加载 messages 并生成 HTML

    Args:
        trace_id: Trace ID
        output_path: 输出 HTML 文件路径
        base_path: Trace 存储根目录
        title: 页面标题，默认使用 trace_id

    Returns:
        输出文件的 Path
    """
    from agent.trace import FileSystemTraceStore

    store = FileSystemTraceStore(base_path=base_path)
    messages = await store.get_trace_messages(trace_id)
    if not messages:
        raise FileNotFoundError(f"Trace {trace_id} 下没有找到 messages")
    page_title = title or f"Trace {trace_id[:8]}... Messages"
    return messages_to_html(messages, output_path, title=page_title)


if __name__ == "__main__":
    import asyncio
    import sys
    from pathlib import Path

    # 添加项目根目录，使 agent 模块可被导入
    _project_root = Path(__file__).resolve().parent.parent.parent
    if str(_project_root) not in sys.path:
        sys.path.insert(0, str(_project_root))

    async def _main():
        import argparse
        parser = argparse.ArgumentParser(description="将 trace messages 转为 HTML")
        parser.add_argument("--trace", required=True, help="Trace ID")
        parser.add_argument("-o", "--output", default="messages.html", help="输出文件路径")
        parser.add_argument("--base-path", default=".trace", help="Trace 存储根目录")
        args = parser.parse_args()

        out = await trace_to_html(args.trace, args.output, base_path=args.base_path)
        print(f"已生成: {out.absolute()}")

    asyncio.run(_main())
