"""主動模式自驗。

驗 4 件事:
  T1. 高信心(明確派工) → 真建任務,但因為 NO_PUSH/quiet 不推 LINE
  T2. 中信心(borderline)→ 不建任務,記 proactive_suggest activity(也因 NO_PUSH 不推)
  T3. 低信心(閒聊)→ 沒動作
  T4. 冷卻:連續 4 次高信心 → 第 4 次應該被冷卻擋(超過 PROACTIVE_MAX_PER_HOUR=3)

跑這支前先設環境覆蓋:
  PROACTIVE_QUIET_START=3 PROACTIVE_QUIET_END=4   # 避開現在時段
  LINE_NO_PUSH=1                                   # 不推真 LINE
"""
import asyncio
import os
import sys
import uuid
from datetime import datetime, timezone

# 載 .env(NAS),但讓我們的 override 蓋過去
ENV = "/volume1/homes/robertsu/coba/app/.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)

# 強制覆蓋(就算 .env 有,我們要這個才能在半夜測)
os.environ["PROACTIVE_QUIET_START"] = "3"
os.environ["PROACTIVE_QUIET_END"] = "4"
os.environ["LINE_NO_PUSH"] = "1"
os.environ["EXTRACTOR_LIVE"] = "true"
os.environ["PROACTIVE_MAX_PER_HOUR"] = "3"
os.environ["PROACTIVE_BUILD_THRESHOLD"] = "0.65"
os.environ["PROACTIVE_SUGGEST_THRESHOLD"] = "0.5"

sys.path.insert(0, "/volume1/homes/robertsu/coba/app")

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


async def _seed_message(channel, sender, text):
    """在 DB 建一則訊息(模擬從 LINE / 網頁進來)。"""
    async with AsyncSessionLocal() as db:
        m = Message(
            channel_id=channel.id,
            user_id=sender.id,
            content=text,
            message_type="text",
            line_message_id="sim_" + uuid.uuid4().hex[:10],
        )
        db.add(m)
        await db.commit()
        await db.refresh(m)
        return m.id


async def _wait_for_activity(activity_type, msg_id, timeout=25):
    """等到 task_extract activity 寫進 DB(代表 Haiku 跑完)。"""
    target_msg = str(msg_id)
    for _ in range(timeout):
        await asyncio.sleep(1)
        async with AsyncSessionLocal() as db:
            rows = (await db.execute(
                select(AgentActivity)
                .where(AgentActivity.activity_type == activity_type)
                .order_by(AgentActivity.created_at.desc())
                .limit(20)
            )).scalars().all()
            for r in rows:
                if (r.extra or {}).get("message_id") == target_msg or \
                   (r.extra or {}).get("from_message_id") == target_msg:
                    return r
    return None


async def _count_tasks_for_msg(msg_id):
    async with AsyncSessionLocal() as db:
        rows = (await db.execute(
            select(AgentActivity).where(
                AgentActivity.activity_type == "task_create_auto",
            )
        )).scalars().all()
    return sum(1 for r in rows if (r.extra or {}).get("from_message_id") == str(msg_id))


async def _force_run_extractor(msg_id):
    """直接呼叫 task_extractor (bypass background scheduling)."""
    from app.services.task_extractor import trigger_task_extract
    await trigger_task_extract(msg_id)


PASS, FAIL = [], []


def check(label, cond, detail=""):
    if cond:
        PASS.append(f"{label} {detail}")
        print(f"[PASS] {label} {detail}")
    else:
        FAIL.append(f"{label} {detail}")
        print(f"[FAIL] {label} {detail}")


async def main():
    print("="*60)
    print("主動模式自驗")
    print(f"  quiet hours: {os.environ['PROACTIVE_QUIET_START']}-{os.environ['PROACTIVE_QUIET_END']}")
    print(f"  build threshold: {os.environ['PROACTIVE_BUILD_THRESHOLD']}")
    print(f"  suggest threshold: {os.environ['PROACTIVE_SUGGEST_THRESHOLD']}")
    print(f"  max per hour: {os.environ['PROACTIVE_MAX_PER_HOUR']}")
    print("="*60)

    # 找 sender + 群組 channel
    async with AsyncSessionLocal() as db:
        robert = (await db.execute(
            select(User).where(User.email == "ssbb30529@gmail.com").limit(1)
        )).scalar_one_or_none()
        ch = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            ).limit(1)
        )).scalar_one_or_none()
    if not robert or not ch:
        print("❌ 缺前置資料")
        return

    print(f"sender={robert.display_name}  channel={ch.name}\n")

    # ---- T1: 高信心 ----
    print("\n>>> T1: 高信心明確派工")
    msg_id = await _seed_message(robert, ch=ch, text="@阿綠 週五前把客戶 A 的提案 v2 寄出去") \
        if False else await _seed_message(ch, robert, "阿綠週五前把客戶 A 的提案 v2 寄出去")
    await _force_run_extractor(msg_id)
    extract_act = await _wait_for_activity("task_extract", msg_id, timeout=20)
    check("T1.1 task_extract activity 有寫", extract_act is not None)
    if extract_act:
        conf = (extract_act.extra or {}).get("confidence")
        is_task = (extract_act.extra or {}).get("is_task")
        print(f"  conf={conf} is_task={is_task} reason={(extract_act.extra or {}).get('reasoning')}")
        check("T1.2 信心 >= 0.65", conf and conf >= 0.65, f"conf={conf}")
    create_act = await _wait_for_activity("task_create_auto", msg_id, timeout=15)
    check("T1.3 真建了 task_create_auto", create_act is not None)
    if create_act:
        check("T1.4 task_id 存在", bool((create_act.extra or {}).get("task_id")))

    # ---- T2: 中信心(borderline)----
    # 一句模糊「下週要把官網改版上線」(沒指人,信心應該中等)
    print("\n>>> T2: 中信心 borderline(主動建議,非建)")
    msg_id2 = await _seed_message(ch, robert, "下週要把官網改版那個上線吧,要不要先盤一下還剩多少")
    await _force_run_extractor(msg_id2)
    extract_act2 = await _wait_for_activity("task_extract", msg_id2, timeout=20)
    check("T2.1 task_extract 有寫", extract_act2 is not None)
    if extract_act2:
        conf2 = (extract_act2.extra or {}).get("confidence", 0) or 0
        print(f"  conf={conf2} reason={(extract_act2.extra or {}).get('reasoning')}")
    # 不一定是 borderline,看 Haiku;不強斷言,只觀察
    # ---- T3: 低信心閒聊 ----
    print("\n>>> T3: 純閒聊")
    msg_id3 = await _seed_message(ch, robert, "今天午餐吃什麼好餓喔")
    await _force_run_extractor(msg_id3)
    extract_act3 = await _wait_for_activity("task_extract", msg_id3, timeout=20)
    check("T3.1 task_extract 有寫", extract_act3 is not None)
    if extract_act3:
        conf3 = (extract_act3.extra or {}).get("confidence", 0) or 0
        is_task3 = (extract_act3.extra or {}).get("is_task")
        print(f"  conf={conf3} is_task={is_task3}")
        check("T3.2 不是 task 或信心 < 0.5", not is_task3 or conf3 < 0.5, f"conf={conf3} is_task={is_task3}")
    # 沒建任務
    create_act3 = await _wait_for_activity("task_create_auto", msg_id3, timeout=5)
    check("T3.3 沒建任務", create_act3 is None)

    # ---- T4: 冷卻 ----
    # 此頻道過去 1 小時的 proactive 行為應該已經 >= 3(T1 算 1),再灌 3 次明確派工
    print("\n>>> T4: 冷卻 — 第 4 次明確派工應建任務但 suppress_reason=cooldown")
    extra_msgs = []
    for i in range(3):
        m = await _seed_message(ch, robert, f"阿綠你今天下班前把報表 #{i} 整理好寄給我")
        await _force_run_extractor(m)
        extra_msgs.append(m)
        await asyncio.sleep(2)

    # 看最後一個的 task_create_auto 是否有 suppress_reason=cooldown
    last = await _wait_for_activity("task_create_auto", extra_msgs[-1], timeout=15)
    check("T4.1 第 N 次任務還是建", last is not None)
    if last:
        sr = (last.extra or {}).get("suppress_reason")
        ns = (last.extra or {}).get("notify_suppressed")
        print(f"  suppress_reason={sr} notify_suppressed={ns}")
        check("T4.2 通知被 suppress(cooldown 或 quiet)", sr in ("cooldown", "quiet_hours"),
              f"sr={sr}")

    print("\n" + "="*60)
    print(f" PASS={len(PASS)} FAIL={len(FAIL)}")
    print("="*60)
    if FAIL:
        sys.exit(1)


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