"""
排程服務(階段 8):每分鐘 tick,跑三件事:
  1. 整點時:檢查任務逾期 / 今天到期 → ping 對應的人(B)
  2. 09:00 Asia/Taipei:每個綁定 LINE 的人收到個人化簡報(C-1)
  3. 18:00 Asia/Taipei:推團隊 wrap-up 到群組(C-2)

設計:
  - 純 asyncio loop,沒有外部依賴(APScheduler / cron)
  - 失敗只 log,不影響其他 tick
  - 「同一事件同一天最多跑一次」用 AgentActivity 表去重
  - 完整跑 = 寫一筆 'task_ping' / 'brief_pushed' / 'wrap_pushed' activity
"""

from __future__ import annotations

import asyncio
import logging
from datetime import date, datetime, timedelta, timezone
from uuid import UUID

from sqlalchemy import select

from app.config import settings
from app.database import AsyncSessionLocal
from app.models.agent_activity import AgentActivity
from app.models.task import Task
from app.models.user import User


logger = logging.getLogger("coba.scheduler")


# Asia/Taipei 時區(NAS 在台灣)
TPE = timezone(timedelta(hours=8))


async def scheduler_loop():
    """每 60 秒 tick 一次。

    階段 9-K(Robert 簡化):
      只保留兩個自動推送,全部進「群組」(管理者看得到):
        1. 08:00  早晨「逾期任務 ping」(@相關人員)
        2. 22:00  晚間「今日完成公告 + 進度%」
      取消所有 1對1 私訊推送(老闆看不到 → 沒管理價值)。
      暫關週六/月底覆盤,等 Robert 開再啟用。
    """
    logger.info("scheduler_loop 啟動 — 模式:群組 only(8:00 逾期 + 21:00 進度 + 22:00 完成)")
    last_morning_overdue: date | None = None
    last_evening_progress: date | None = None
    last_evening_recap: date | None = None

    while True:
        try:
            await asyncio.sleep(60)
            now = datetime.now(TPE)
            today = now.date()

            # === 1. 08:00 早晨群組「逾期 ping」===
            if now.hour == 8 and now.minute < 5 and last_morning_overdue != today:
                logger.info("[scheduler] morning overdue group-ping")
                try:
                    await _run_morning_overdue_group_ping()
                except Exception:
                    logger.exception("morning overdue ping failed")
                last_morning_overdue = today

            # === 2. 21:00 晚間「今天還沒完成的進度 ping」(階段 10-I)===
            if now.hour == 21 and now.minute < 5 and last_evening_progress != today:
                logger.info("[scheduler] evening progress ping")
                try:
                    await _run_evening_progress_ping()
                except Exception:
                    logger.exception("evening progress ping failed")
                last_evening_progress = today

            # === 3. 22:00 晚間群組「今日完成公告」===
            if now.hour == 22 and now.minute < 5 and last_evening_recap != today:
                logger.info("[scheduler] evening completion announcement")
                try:
                    await _run_evening_completion_announcement()
                except Exception:
                    logger.exception("evening recap failed")
                last_evening_recap = today

        except asyncio.CancelledError:
            logger.info("scheduler_loop 收到 cancel,退出")
            break
        except Exception:
            logger.exception("scheduler tick 例外(繼續下一分鐘)")


# ============================================================
# 階段 9-K:群組「08:00 逾期 ping」+「22:00 今日完成公告」
# ============================================================


async def _run_morning_overdue_group_ping() -> None:
    """08:00 推群組,公告所有逾期未完成任務 + 點名 assignee。

    格式:
      ☀️ 早安,以下是逾期任務需要回報:

      神狙手阿綠
        • 《X》(原訂 5/7,已逾期 1 天)
        • 《Y》(原訂 5/5,已逾期 3 天)

      Jonina🕊️
        • 《Z》(原訂 5/6,已逾期 2 天)

      請各自回報目前狀況。沒人逾期就不推。
    """
    from app.services import line_client
    from app.models.channel import Channel

    today = datetime.now(TPE).date()

    async with AsyncSessionLocal() as db:
        # 撈逾期未完成任務(due_date < today,status 非 done)
        tasks = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.status.in_(["todo", "in_progress", "review"]),
                Task.due_date.is_not(None),
                Task.due_date < today,
            )
        )).scalars().all()

        if not tasks:
            logger.info("[morning ping] 沒逾期任務,不推")
            return

        # assignee 分組
        groups_by_user: dict = {}
        users_needed = set()
        for t in tasks:
            uid = t.assignee_id or t.created_by
            if not uid:
                continue
            users_needed.add(uid)
            groups_by_user.setdefault(uid, []).append(t)
        if not groups_by_user:
            return

        users_map = {}
        if users_needed:
            us = (await db.execute(select(User).where(User.id.in_(users_needed)))).scalars().all()
            users_map = {u.id: u for u in us}

        # 取群組(只推「群組」channel,1對1 不推)
        chans = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            )
        )).scalars().all()

    if not chans:
        logger.info("[morning ping] 沒 LINE 群組可推")
        return

    # 組訊息
    lines = ["☀️ 早安,以下是逾期任務需要回報:\n"]
    total_count = 0
    for uid, ts in groups_by_user.items():
        u = users_map.get(uid)
        name = u.display_name if u else "(未知)"
        lines.append(f"\n{name}")
        for t in ts:
            days_late = (today - t.due_date).days
            lines.append(f"  • 《{t.title}》(原訂 {t.due_date.isoformat()},已逾期 {days_late} 天)")
            total_count += 1
    lines.append(f"\n請各自回報目前狀況(回「庫柏 X 任務完成」我幫你改 / 「庫柏 X 延到 Y/Z」我幫你改截止)。")

    text = "\n".join(lines)

    for ch in chans:
        if not ch.line_source_id:
            continue
        ok = await line_client.push_text(ch.line_source_id, text)
        if ok:
            async with AsyncSessionLocal() as db:
                db.add(AgentActivity(
                    activity_type="morning_overdue_ping",
                    summary=f"早晨逾期 ping 推群({total_count} 件,{len(groups_by_user)} 人)",
                    extra={
                        "channel_name": ch.name,
                        "task_count": total_count,
                        "user_count": len(groups_by_user),
                    },
                ))
                await db.commit()


async def _run_evening_progress_ping() -> None:
    """階段 10-I:21:00 推群組,@ 今天 due 還沒完成的人,問進度。

    跟 08:00 的差別:
      - 08:00 ping「逾期」(due < today,過期未做)
      - 21:00 ping「今天還沒完成」(due == today,當天還沒做完)

    沒人有今天 due 未完成 → 不推。
    """
    from app.services import line_client
    from app.models.channel import Channel

    today = datetime.now(TPE).date()

    async with AsyncSessionLocal() as db:
        # 撈今天 due 但還沒 done 的任務
        tasks = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.status.in_(["todo", "in_progress", "review"]),
                Task.due_date == today,
            )
        )).scalars().all()

        if not tasks:
            logger.info("[evening progress ping] 今天沒待完成任務,不推")
            return

        # 按 assignee 分組
        groups_by_user: dict = {}
        users_needed = set()
        for t in tasks:
            uid = t.assignee_id or t.created_by
            if not uid:
                continue
            users_needed.add(uid)
            groups_by_user.setdefault(uid, []).append(t)
        if not groups_by_user:
            return

        users_map = {}
        if users_needed:
            us = (await db.execute(select(User).where(User.id.in_(users_needed)))).scalars().all()
            users_map = {u.id: u for u in us}

        # 取 LINE 群組 channels
        chans = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            )
        )).scalars().all()

    if not chans:
        logger.info("[evening progress ping] 沒 LINE 群組可推")
        return

    # 組訊息
    lines = ["🌆 晚上 9 點,今天還沒完成的任務確認一下進度:\n"]
    total_count = 0
    for uid, ts in groups_by_user.items():
        u = users_map.get(uid)
        name = u.display_name if u else "(未知)"
        lines.append(f"\n@{name}")
        for t in ts:
            lines.append(f"  • 《{t.title}》")
            total_count += 1
    lines.append(
        "\n各位回報一下今天有沒有做完 —\n"
        "完成的回「庫柏 X 完成」,我幫你標 done。\n"
        "做不完延期回「庫柏 X 延到 Y/Z」,我幫你改截止日。"
    )

    text = "\n".join(lines)

    for ch in chans:
        if not ch.line_source_id:
            continue
        ok = await line_client.push_text(ch.line_source_id, text)
        if ok:
            async with AsyncSessionLocal() as db:
                db.add(AgentActivity(
                    activity_type="evening_progress_ping",
                    summary=f"晚間 9 點進度 ping({total_count} 件,{len(groups_by_user)} 人)",
                    extra={
                        "channel_name": ch.name,
                        "task_count": total_count,
                        "user_count": len(groups_by_user),
                    },
                ))
                await db.commit()


async def _run_evening_completion_announcement() -> None:
    """22:00 推群組,公告今日完成情況 + 進度%。

    格式:
      🌙 今日工作公告

      今天完成 X / 應完成 Y(Z%)

      ✅ 完成清單:
        • 神狙手阿綠 — 《A》《B》
        • Jonina — 《C》

      ⏳ 還沒完成的(明早 8:00 我會再 ping):
        • 盛豪 — 《D》
        • Robert Su — 《E》

      整體進度:N 件 / 完成 M / 進行中 K(L%)
    """
    from app.services import line_client
    from app.models.channel import Channel

    today = datetime.now(TPE).date()
    today_start_utc = datetime.combine(today, datetime.min.time(), tzinfo=TPE).astimezone(timezone.utc)

    async with AsyncSessionLocal() as db:
        # 1. 今天 due 的任務(應該完成的)
        due_today = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.due_date == today,
            )
        )).scalars().all()
        # 2. 今天「已完成」的(completed_at 在今天)
        done_today = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.status == "done",
                Task.completed_at >= today_start_utc,
            )
        )).scalars().all()
        # 3. 整體狀態
        total_active = (await db.execute(
            select(Task).where(Task.deleted_at.is_(None))
        )).scalars().all()

        # 找 user 名字
        users_needed = set()
        for t in due_today + done_today:
            uid = t.assignee_id or t.created_by
            if uid:
                users_needed.add(uid)
        users_map = {}
        if users_needed:
            us = (await db.execute(select(User).where(User.id.in_(users_needed)))).scalars().all()
            users_map = {u.id: u.display_name for u in us}

        # group 撈
        chans = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            )
        )).scalars().all()

    if not chans:
        return

    # 統計
    today_done_count = sum(1 for t in due_today if t.status == "done")
    today_due_count = len(due_today)
    today_pct = (today_done_count / today_due_count * 100) if today_due_count else 0

    overall_total = len(total_active)
    overall_done = sum(1 for t in total_active if t.status == "done")
    overall_doing = sum(1 for t in total_active if t.status == "in_progress")
    overall_pct = (overall_done / overall_total * 100) if overall_total else 0

    # 組訊息
    lines = ["🌙 今日工作公告\n"]

    if today_due_count > 0:
        lines.append(f"今天應完成 {today_due_count} / 完成 {today_done_count}({today_pct:.0f}%)\n")
    elif done_today:
        lines.append(f"今天完成 {len(done_today)} 件(雖然不是今天 due,但也算進度)\n")
    else:
        lines.append("今天沒有任務 due,大家都很閒嗎?😄\n")

    # 完成清單(group by assignee)
    if done_today:
        by_user = {}
        for t in done_today:
            uid = t.assignee_id or t.created_by
            by_user.setdefault(uid, []).append(t)
        lines.append("✅ 完成:")
        for uid, ts in by_user.items():
            name = users_map.get(uid, "(未知)")
            titles = " / ".join(f"《{t.title}》" for t in ts)
            lines.append(f"  • {name} — {titles}")

    # 今日 due 但沒完成的(明早 8:00 會再 ping)
    pending = [t for t in due_today if t.status != "done"]
    if pending:
        lines.append("\n⏳ 還沒完成(明早 8:00 我會再 ping):")
        by_user = {}
        for t in pending:
            uid = t.assignee_id or t.created_by
            by_user.setdefault(uid, []).append(t)
        for uid, ts in by_user.items():
            name = users_map.get(uid, "(未知)")
            titles = " / ".join(f"《{t.title}》" for t in ts)
            lines.append(f"  • {name} — {titles}")

    # 整體
    lines.append(f"\n📊 整體進度:{overall_total} 件總計 / 完成 {overall_done}({overall_pct:.0f}%)/ 進行中 {overall_doing}")

    text = "\n".join(lines)

    for ch in chans:
        if not ch.line_source_id:
            continue
        ok = await line_client.push_text(ch.line_source_id, text)
        if ok:
            async with AsyncSessionLocal() as db:
                db.add(AgentActivity(
                    activity_type="evening_completion",
                    summary=f"晚間完成公告({today_done_count}/{today_due_count} = {today_pct:.0f}%)",
                    extra={
                        "channel_name": ch.name,
                        "today_due": today_due_count,
                        "today_done": today_done_count,
                        "today_pct": round(today_pct, 1),
                        "overall_pct": round(overall_pct, 1),
                    },
                ))
                await db.commit()


async def _already_pinged_today(task_id: UUID, db) -> bool:
    """今天有沒有 ping 過這個 task。"""
    today_start = datetime.combine(datetime.now(TPE).date(), datetime.min.time(), tzinfo=TPE)
    rows = (await db.execute(
        select(AgentActivity)
        .where(
            AgentActivity.activity_type == "task_ping",
            AgentActivity.created_at >= today_start.astimezone(timezone.utc),
        )
    )).scalars().all()
    for r in rows:
        if r.extra and str(r.extra.get("task_id")) == str(task_id):
            return True
    return False


async def _run_ping_check() -> None:
    """掃任務,逾期 / 今天到期且 assignee 有綁 LINE → 推一則。"""
    from app.services import line_client

    today = datetime.now(TPE).date()
    yesterday = today - timedelta(days=1)

    async with AsyncSessionLocal() as db:
        # 抓所有未完成、有截止日、指派給人的任務
        tasks = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.status.in_(["todo", "in_progress", "review"]),
                Task.due_date.is_not(None),
                Task.assignee_id.is_not(None),
            )
        )).scalars().all()

        target_tasks: list[tuple[Task, str]] = []   # (task, urgency_label)
        for t in tasks:
            if t.due_date < today:
                target_tasks.append((t, "逾期"))
            elif t.due_date == today:
                target_tasks.append((t, "今天到期"))

        if not target_tasks:
            logger.info("[ping] 沒有需要 ping 的任務")
            return

        # 抓 assignee 資訊
        assignee_ids = {t.assignee_id for t, _ in target_tasks}
        users = (await db.execute(
            select(User).where(User.id.in_(assignee_ids), User.deleted_at.is_(None))
        )).scalars().all()
        user_map = {u.id: u for u in users}

        # 按負責人 group(每人收一則彙整訊息,而不是每任務一則)
        per_user: dict[UUID, list[tuple[Task, str]]] = {}
        for t, label in target_tasks:
            u = user_map.get(t.assignee_id)
            if not u or not u.line_user_id:
                continue
            # 跳過已 ping 過的
            if await _already_pinged_today(t.id, db):
                continue
            per_user.setdefault(u.id, []).append((t, label))

        for uid, items in per_user.items():
            u = user_map[uid]
            text = f"⏰ {u.display_name},任務提醒:\n\n"
            for t, label in items:
                emoji = "🔴" if label == "逾期" else "🟡"
                pri = {"urgent": "⚡", "high": "🔥", "medium": "", "low": "💤"}.get(t.priority, "")
                text += f"{emoji} 《{t.title}》{label} {pri}\n"
            text += f"\n做完直接跟我說「庫柏 X 任務完成」,我幫你改狀態。"

            ok = await line_client.push_text(u.line_user_id, text)

            if ok:
                # 記每個 task 一筆,下次去重才會準
                for t, _ in items:
                    db.add(AgentActivity(
                        activity_type="task_ping",
                        user_id=uid,
                        summary=f"提醒 {u.display_name}:{t.title}",
                        extra={"task_id": str(t.id), "label": _},
                    ))
                await db.commit()
                logger.info("[ping] 推給 %s,%d 件任務", u.display_name, len(items))


# ============================================================
# C-1:09:00 個人化簡報
# ============================================================


async def _run_morning_briefs() -> None:
    """每個綁了 LINE 的人收到 daily_brief。"""
    from app.services import line_client
    from app.services.daily_brief import generate_daily_brief

    async with AsyncSessionLocal() as db:
        users = (await db.execute(
            select(User).where(
                User.is_active.is_(True),
                User.deleted_at.is_(None),
                User.line_user_id.is_not(None),
                User.password_hash != "",   # 影子帳號跳過
            )
        )).scalars().all()

    for u in users:
        try:
            result = await generate_daily_brief(u)
            brief_text = result.get("brief", "")
            if not brief_text:
                continue
            text = f"☀️ 早安 {u.display_name}\n\n{brief_text}"
            ok = await line_client.push_text(u.line_user_id, text)
            if ok:
                async with AsyncSessionLocal() as db:
                    db.add(AgentActivity(
                        activity_type="brief_generated",
                        user_id=u.id,
                        summary=f"早晨簡報推送給 {u.display_name}",
                        extra={"channel": "morning_brief", "preview": brief_text[:120]},
                    ))
                    await db.commit()
        except Exception:
            logger.exception("morning brief for %s failed", u.display_name)


# ============================================================
# C-2:18:00 團隊 wrap-up
# ============================================================


async def _run_evening_wrap() -> None:
    """推團隊 wrap-up 到所有 LINE 群組(目前可能只有一個群)。"""
    import anthropic
    from app.models.channel import Channel
    from app.services import line_client

    async with AsyncSessionLocal() as db:
        # 找所有 LINE 群組頻道
        groups = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            )
        )).scalars().all()
        if not groups:
            logger.info("[wrap] 沒 LINE 群,跳過")
            return

        # 抓今天的任務變化(建立 / 完成 / 進行中)
        today = datetime.now(TPE).date()
        today_utc_start = datetime.combine(today, datetime.min.time(), tzinfo=TPE).astimezone(timezone.utc)

        tasks_today = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.created_at >= today_utc_start,
            )
        )).scalars().all()

        completed_today = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.completed_at.is_not(None),
                Task.completed_at >= today_utc_start,
            )
        )).scalars().all()

        in_progress = (await db.execute(
            select(Task).where(
                Task.deleted_at.is_(None),
                Task.status == "in_progress",
            )
        )).scalars().all()

    # 用 Haiku 生成簡短 wrap-up
    if not settings.ANTHROPIC_API_KEY:
        return
    summary_input = (
        f"今天建了 {len(tasks_today)} 個新任務,完成 {len(completed_today)} 個,"
        f"目前進行中 {len(in_progress)} 個。\n\n"
        f"今天新建:\n" + "\n".join(f"- {t.title}" for t in tasks_today[:8]) +
        f"\n\n今天完成:\n" + "\n".join(f"- {t.title}" for t in completed_today[:8])
    )
    try:
        client = anthropic.AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
        resp = await client.messages.create(
            model=settings.CLAUDE_MODEL_HAIKU,
            max_tokens=400,
            system=(
                "你是庫柏 — 山水話團隊 AI 同事。寫一則 18:00 wrap-up 訊息推到 LINE 群,"
                "提綱挈領、口語、4-6 行內。讓老闆 + 員工知道今天團隊整體狀況。"
                "如果沒進度 / 沒完成 → 直接說明天加油,不要硬擠。"
            ),
            messages=[{"role": "user", "content": summary_input}],
        )
        text = "🌙 今日 wrap-up\n\n" + "".join(b.text for b in resp.content if b.type == "text").strip()
    except Exception:
        logger.exception("wrap-up Claude 失敗")
        text = (
            f"🌙 今日 wrap-up\n\n"
            f"建了 {len(tasks_today)} 件任務、完成 {len(completed_today)} 件、"
            f"進行中 {len(in_progress)} 件。明天加油 💪"
        )

    for ch in groups:
        if ch.line_source_id:
            ok = await line_client.push_text(ch.line_source_id, text)
            if ok:
                async with AsyncSessionLocal() as db:
                    db.add(AgentActivity(
                        activity_type="brief_generated",
                        summary=f"晚間 wrap-up 推到群 {ch.name}",
                        extra={"channel": "evening_wrap", "channel_name": ch.name},
                    ))
                    await db.commit()


# ============================================================
# D:每週六 18:30 推「本週覆盤總結」
# ============================================================


async def _build_recap(period_label: str, since_dt: datetime) -> tuple[str, dict]:
    """共用邏輯:撈 since_dt 後的 reflection_note,生成 LINE 貼文。
    回 (text, stats)。"""
    import anthropic
    from app.services import line_client
    from app.models.channel import Channel
    from collections import Counter

    async with AsyncSessionLocal() as db:
        notes = (await db.execute(
            select(AgentActivity)
            .where(
                AgentActivity.activity_type == "reflection_note",
                AgentActivity.created_at >= since_dt,
            )
            .order_by(AgentActivity.created_at.desc())
        )).scalars().all()

        # 統計每人筆數 + 抓 user 名字
        per_user = Counter()
        for n in notes:
            if n.user_id:
                per_user[n.user_id] += 1
        users_map = {}
        if per_user:
            us = (await db.execute(
                select(User).where(User.id.in_(list(per_user.keys())))
            )).scalars().all()
            users_map = {u.id: u.display_name for u in us}

        # 抓「重複犯錯警訊」:有 similar_to 標記的
        with_similar = [n for n in notes if (n.extra or {}).get("similar_count", 0) > 0]

        # tag 分布
        tag_counter = Counter()
        for n in notes:
            t = (n.extra or {}).get("tag")
            if t:
                tag_counter[t] += 1

    if not notes:
        return f"🌱 {period_label}覆盤\n\n這段時間群組裡還沒有人分享體悟。下週多分享一些學到的事吧 💡", {"count": 0}

    # 排序貢獻者
    top_contributors = per_user.most_common(5)

    # Sonnet 生成「認知提升摘要」+ 共通主題 top 3
    summary_input_lines = []
    for n in notes[:30]:                      # 最多 30 則送進 prompt
        author = users_map.get(n.user_id, "(未知)")
        summary_input_lines.append(
            f"- [{author}][{(n.extra or {}).get('tag') or '其他'}] {(n.extra or {}).get('summary') or n.summary}"
        )
    summary_input = "\n".join(summary_input_lines)

    repeat_warnings_text = ""
    if with_similar:
        repeat_warnings_text = "\n\n⚠ 系統發現以下體悟跟過去 30 天的某些體悟很像(可能在重複學同一個教訓):\n"
        for n in with_similar[:5]:
            ext = n.extra or {}
            repeat_warnings_text += f"  - [{users_map.get(n.user_id, '?')}] {ext.get('summary')[:40]} (相似度 {ext.get('similar_to', [{}])[0].get('score', '?')})\n"

    text = ""
    if settings.ANTHROPIC_API_KEY:
        try:
            client = anthropic.AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
            resp = await client.messages.create(
                model=settings.CLAUDE_MODEL_SONNET,
                max_tokens=900,
                system=(
                    "你是庫柏 — 山水話團隊 AI 同事。"
                    "下面是團隊這段時間在 LINE 群組分享的體悟列表。"
                    f"請寫一則「{period_label}覆盤總結」推到 LINE 群,500 字內。"
                    "結構:\n"
                    "1. 一句話定調這段期間整體認知方向(從哪 → 學到什麼)\n"
                    "2. 共通主題 top 3(用 bullet,每點 15 字內)\n"
                    "3. 認知提升亮點(挑 1-2 則最深的體悟,引用作者 + 一句話)\n"
                    "4. 「踩坑警訊」:如有重複學同一教訓的訊號,點名提醒\n"
                    "5. 結尾鼓勵 1 行\n"
                    "口語、不裝、直接、要像同事在說話而非報告。"
                ),
                messages=[{"role": "user", "content":
                    f"【{period_label}體悟列表】\n{summary_input}\n"
                    f"\n【貢獻者排名】\n" +
                    "\n".join(f"  {i+1}. {users_map.get(uid,'?')}: {n} 則"
                             for i, (uid, n) in enumerate(top_contributors)) +
                    repeat_warnings_text
                }],
            )
            text = "".join(b.text for b in resp.content if b.type == "text").strip()
        except Exception:
            logger.exception(f"{period_label}覆盤 Sonnet 失敗")

    # fallback:純資料
    if not text:
        text = f"📊 {period_label}覆盤\n\n共 {len(notes)} 則體悟。\n貢獻 top 3:\n"
        for i, (uid, c) in enumerate(top_contributors[:3], 1):
            text += f"  {i}. {users_map.get(uid, '?')}: {c} 則\n"
        if tag_counter:
            text += f"\n主要 tag:{', '.join(t for t,_ in tag_counter.most_common(3))}"

    # header 加 emoji
    header = f"📊 {period_label}覆盤總結\n共 {len(notes)} 則體悟,系統幫你整理:\n\n"
    return header + text, {"count": len(notes), "top_users": top_contributors,
                            "tags": dict(tag_counter), "similar_count": len(with_similar)}


async def _push_recap_to_groups(text: str, stats: dict, recap_type: str):
    from app.services import line_client
    from app.models.channel import Channel

    async with AsyncSessionLocal() as db:
        groups = (await db.execute(
            select(Channel).where(
                Channel.line_source_type == "group",
                Channel.deleted_at.is_(None),
            )
        )).scalars().all()

    for ch in groups:
        if not ch.line_source_id:
            continue
        ok = await line_client.push_text(ch.line_source_id, text)
        if ok:
            async with AsyncSessionLocal() as db:
                db.add(AgentActivity(
                    activity_type=recap_type,
                    summary=f"{recap_type} 推到群 {ch.name}({stats.get('count',0)} 則體悟)",
                    extra={"channel_name": ch.name, **stats},
                ))
                await db.commit()


async def _run_weekly_recap():
    since = datetime.now(timezone.utc) - timedelta(days=7)
    text, stats = await _build_recap("本週", since)
    await _push_recap_to_groups(text, stats, recap_type="weekly_recap")


async def _run_monthly_recap():
    since = datetime.now(timezone.utc) - timedelta(days=30)
    text, stats = await _build_recap("本月", since)
    await _push_recap_to_groups(text, stats, recap_type="monthly_recap")
