"""
模擬 LINE webhook 訊息進系統,驗 Cooper 行為(不打擾真實 LINE 群)。

用法(在 NAS 上跑):
    cd /volume1/homes/robertsu/coba/app
    .venv/bin/python tools/sim_line.py --user Robert --text "庫柏 列阿綠的任務"
    .venv/bin/python tools/sim_line.py --user Robert --text "幫阿綠建任務《X》" --no-push
    .venv/bin/python tools/sim_line.py --dm --user Robert --text "你能幹嘛?"

開發機上推到 NAS 跑也行,看你方便。

NO_PUSH 環境變數:設成 1 → Cooper 回應只存 DB,不推到真 LINE 群(避免洗群)
"""
from __future__ import annotations

import argparse
import asyncio
import os
import sys
import uuid
from datetime import datetime, timezone

# 讓 import app 可用
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 載入 .env(NAS 部署時)
ENV = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
if os.path.exists(ENV):
    for line in open(ENV):
        if "=" in line and not line.startswith("#"):
            k, v = line.strip().split("=", 1)
            os.environ.setdefault(k, v)


async def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--user", required=True, help="display_name (Robert / Jonina / 神狙手阿綠 / 部分相符)")
    ap.add_argument("--text", required=True, help="訊息內容")
    ap.add_argument("--dm", action="store_true", help="模擬 1對1 私訊(預設群組)")
    ap.add_argument("--no-push", action="store_true", help="Cooper 回應不真推 LINE,只存 DB")
    ap.add_argument("--show-context", action="store_true", help="也印過去 5 則歷史訊息")
    args = ap.parse_args()

    if args.no_push:
        os.environ["LINE_NO_PUSH"] = "1"

    from sqlalchemy import select
    from app.database import AsyncSessionLocal
    from app.models.channel import Channel, Message
    from app.models.user import User
    from app.models.task import Task
    from app.models.agent_activity import AgentActivity

    # 找 user
    async with AsyncSessionLocal() as db:
        u = (await db.execute(
            select(User).where(User.display_name.contains(args.user), User.deleted_at.is_(None))
            .limit(1)
        )).scalar_one_or_none()
        if u is None:
            print(f"❌ 找不到 user 含 '{args.user}'")
            return
        if not u.line_user_id:
            print(f"⚠ {u.display_name} 還沒綁 LINE,模擬可能不完整")

        # 找 channel
        ch_type = "user" if args.dm else "group"
        ch = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == ch_type,
                Channel.deleted_at.is_(None),
            ).limit(1)
        )).scalar_one_or_none()
        if ch is None:
            print(f"❌ 找不到 line_source_type='{ch_type}' 的 channel")
            return

        line_msg_id = "sim_" + uuid.uuid4().hex[:12]

    # 構造 LINE webhook event
    event = {
        "type": "message",
        "replyToken": "sim_token_" + uuid.uuid4().hex[:8],
        "source": (
            {"type": "user", "userId": u.line_user_id or "U_unknown"}
            if args.dm else
            {"type": "group", "groupId": ch.line_source_id, "userId": u.line_user_id or "U_unknown"}
        ),
        "message": {"type": "text", "id": line_msg_id, "text": args.text},
    }

    print(f"\n{'='*60}")
    print(f"模擬訊息:[{u.display_name}] {args.text}")
    print(f"  channel: {ch.name} ({ch.line_source_type})")
    print(f"  no-push: {args.no_push}")
    print(f"{'='*60}\n")

    if args.show_context:
        async with AsyncSessionLocal() as db:
            recent = (await db.execute(
                select(Message)
                .where(Message.channel_id == ch.id, Message.deleted_at.is_(None))
                .order_by(Message.created_at.desc())
                .limit(5)
            )).scalars().all()
            print("最近 5 則 context:")
            for m in reversed(recent):
                speaker = "庫柏" if m.user_id is None else (await _get_name(m.user_id))
                print(f"  [{m.created_at.strftime('%H:%M')}] {speaker}: {m.content[:60]}")
            print()

    before_tasks = await _count_tasks()
    before_activities = await _count_activities()
    # 紀錄目前最新 Cooper 訊息的 id,等下用來確認有沒有「新」回應
    before_coba_msg_id = await _latest_coba_msg_id(ch.id)

    # 跑 Cooper
    from app.services import line_bridge
    await line_bridge.handle_event(event)

    # Polling:等到 Cooper 真的回應(最多 60 秒)
    print("⏳ 等 Cooper 回應...", end="", flush=True)
    for i in range(60):
        await asyncio.sleep(1)
        latest = await _latest_coba_msg_id(ch.id)
        if latest and latest != before_coba_msg_id:
            print(f" 收到({i+1}s)")
            break
        print(".", end="", flush=True)
    else:
        print(" ⏱ 超過 60 秒沒新回應")
    # 再多等 5 秒讓 AgentActivity 跟 trailing tool calls 寫完
    await asyncio.sleep(5)

    # 看結果
    async with AsyncSessionLocal() as db:
        # Cooper 最新回應
        coba_msg = (await db.execute(
            select(Message)
            .where(Message.user_id.is_(None), Message.channel_id == ch.id, Message.deleted_at.is_(None))
            .order_by(Message.created_at.desc()).limit(1)
        )).scalars().first()
        if coba_msg:
            print("📨 Cooper 回應:")
            print("─" * 60)
            print(coba_msg.content)
            print("─" * 60)
            # 偵測異常
            if "<function_calls>" in coba_msg.content or "<invoke" in coba_msg.content:
                print("\n⚠⚠⚠ 回應包含 XML 假 tool call!Bug 還在")
            else:
                print("\n✓ 回應沒有 XML 文字 tool call")

    # 任務變化
    after_tasks = await _count_tasks()
    if after_tasks > before_tasks:
        print(f"\n📋 新建任務 {after_tasks - before_tasks} 筆:")
        async with AsyncSessionLocal() as db:
            new_tasks = (await db.execute(
                select(Task).where(Task.deleted_at.is_(None))
                .order_by(Task.created_at.desc()).limit(after_tasks - before_tasks)
            )).scalars().all()
            for t in new_tasks:
                assignee = "(未指派)"
                if t.assignee_id:
                    au = (await db.execute(select(User).where(User.id == t.assignee_id))).scalar_one_or_none()
                    if au: assignee = au.display_name
                print(f"  • {t.title} | {t.priority} | due={t.due_date} | assignee={assignee}")
    else:
        print("\n📋 沒新建任務")

    # 活動
    after_activities = await _count_activities()
    diff = after_activities - before_activities
    if diff > 0:
        print(f"\n🤖 庫柏動態新增 {diff} 筆:")
        async with AsyncSessionLocal() as db:
            new_acts = (await db.execute(
                select(AgentActivity).order_by(AgentActivity.created_at.desc()).limit(diff)
            )).scalars().all()
            for a in reversed(new_acts):
                cost = f" ${a.cost_usd:.4f}" if a.cost_usd else ""
                print(f"  • [{a.activity_type}] {a.summary}{cost}")
                if a.activity_type == "line_response" and a.extra and a.extra.get("tool_calls"):
                    for tc in a.extra["tool_calls"]:
                        print(f"      ↪ tool_call: {tc.get('name')}({tc.get('input')})")

    print()


async def _latest_coba_msg_id(channel_id):
    from sqlalchemy import select
    from app.database import AsyncSessionLocal
    from app.models.channel import Message
    async with AsyncSessionLocal() as db:
        m = (await db.execute(
            select(Message)
            .where(Message.user_id.is_(None), Message.channel_id == channel_id, Message.deleted_at.is_(None))
            .order_by(Message.created_at.desc())
            .limit(1)
        )).scalars().first()
        return m.id if m else None


async def _count_tasks() -> int:
    from sqlalchemy import select, func
    from app.database import AsyncSessionLocal
    from app.models.task import Task
    async with AsyncSessionLocal() as db:
        return (await db.execute(select(func.count()).select_from(Task).where(Task.deleted_at.is_(None)))).scalar()


async def _count_activities() -> int:
    from sqlalchemy import select, func
    from app.database import AsyncSessionLocal
    from app.models.agent_activity import AgentActivity
    async with AsyncSessionLocal() as db:
        return (await db.execute(select(func.count()).select_from(AgentActivity))).scalar()


async def _get_name(user_id) -> str:
    from sqlalchemy import select
    from app.database import AsyncSessionLocal
    from app.models.user import User
    async with AsyncSessionLocal() as db:
        u = (await db.execute(select(User).where(User.id == user_id))).scalar_one_or_none()
        return u.display_name if u else "(未知)"


if __name__ == "__main__":
    asyncio.run(main())
