"""Backfill tool 消息 payload 缺失的 `name` 字段。

背景:loop.py 早期 append tool 消息时只写了 role/tool_call_id/content,
没存 name。前端依赖 `payload.name` 判断是不是产物工具(seedream/seedance)
→ 历史 task 重新打开时 banner/chip 不显示。本脚本一次性回填。

策略:按 task 走,先把每条 assistant 消息里 tool_calls[].id → name 收集
成 map;再扫该 task 内 role=tool 的消息,按 tool_call_id 查 map 补 name。
查不到的(罕见:LLM 给的 id 跨 task 不对齐 / 历史脏数据)打 warn 跳过。

跑法: .venv/Scripts/python.exe scripts/backfill_tool_message_name.py
      默认 dry-run,加 --apply 真写。

幂等:已经有 name 的消息跳过;再跑一遍 0 改动。
"""
from __future__ import annotations

import argparse
import os
import sys
from collections import defaultdict
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

env_file = ROOT / ".env"
if env_file.exists():
    for line in env_file.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, _, v = line.partition("=")
        os.environ.setdefault(k.strip(), v.strip())

from sqlalchemy import select
from sqlalchemy.orm.attributes import flag_modified

from core.storage import session_scope
from core.storage.models import Message


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--apply", action="store_true", help="真写;默认 dry-run 只打印")
    args = ap.parse_args()

    n_tool = 0           # 扫到的 tool 消息总数
    n_already = 0        # 已有 name 跳过
    n_filled = 0         # 本次补上
    n_unresolved = 0     # 查不到 name 的(打 warn)

    with session_scope() as s:
        rows = s.execute(select(Message).order_by(Message.task_id, Message.idx)).scalars().all()

        # tool_call_id → name(按 task 分组,避免跨 task 撞 id)
        by_task: dict[str, dict[str, str]] = defaultdict(dict)
        for m in rows:
            p = m.payload or {}
            if p.get("role") != "assistant":
                continue
            tcs = p.get("tool_calls") or []
            for tc in tcs:
                tcid = tc.get("id")
                fn = (tc.get("function") or {}).get("name")
                if tcid and fn:
                    by_task[str(m.task_id)][tcid] = fn

        for m in rows:
            p = m.payload or {}
            if p.get("role") != "tool":
                continue
            n_tool += 1
            if p.get("name"):
                n_already += 1
                continue
            tcid = p.get("tool_call_id")
            name = by_task.get(str(m.task_id), {}).get(tcid or "")
            if not name:
                n_unresolved += 1
                print(f"  [WARN] task={m.task_id} idx={m.idx} tool_call_id={tcid!r} 找不到对应 assistant tool_call,跳过")
                continue
            print(f"  [FILL] task={m.task_id} idx={m.idx} <- name={name!r}")
            p["name"] = name
            m.payload = p
            flag_modified(m, "payload")
            n_filled += 1

        if args.apply:
            s.commit()
        else:
            s.rollback()

    print()
    print(f"[summary] tool messages={n_tool} | already={n_already} | "
          f"filled={n_filled} | unresolved={n_unresolved}")
    print(f"[mode] {'APPLIED (committed)' if args.apply else 'DRY-RUN (no commit, rerun with --apply)'}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
