"""任務認領偵測(階段 9-B)。

情境:
  Robert 在 LINE 群組:「阿綠 週五前把客戶 A 的提案 v3 寄出去」
  → task_extractor 自動建任務 `<X>`,assignee=阿綠,寫 task_create_auto activity
  阿綠回:「OK 收到」
  → 這支服務:看到阿綠在「同 channel」「24 hr 內」「對應自己被派的任務」回了確認語,
     寫 `task_accepted` activity 標記認領

判斷邏輯:
  1. 訊息進來,只看 user_id != null(庫柏自己跳過)
  2. 字數 < 30 才有可能是「短確認」(回長段討論不算)
  3. 關鍵字 prefilter:含「OK / 好 / 收到 / 沒問題 / 了解 / 收 / 接 / 行 / 知道」之一
  4. 找最近 24hr 內 task_create_auto activities,assignee_id == sender_id 且 channel_id 相同
  5. 對每筆候選用 Haiku 判斷「這訊息算對該任務的接受嗎」(避免「不接」「沒空接」誤判)
  6. 信心 ≥ 0.7 → 寫 task_accepted activity,extra 含 task_id, source_msg_id, confidence

判斷加速:
  - 沒候選任務 → 完全不打 Haiku
  - 多個候選一次給 Haiku 判斷
"""
from __future__ import annotations

import json
import logging
import os
import re
from datetime import datetime, timedelta, timezone
from uuid import UUID

import anthropic
from sqlalchemy import select

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


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

# 關鍵字 prefilter:含這些才考慮做認領判斷
_ACCEPT_HINTS = [
    "ok", "OK", "Ok",
    "好", "好的", "好喔", "好阿", "好啊",
    "收到", "已收", "收下", "已收到",
    "沒問題", "no problem", "沒事",
    "了解", "懂了", "知道",
    "可以", "行", "成", "可",
    "接", "接下", "我接", "我來",
    "👌", "✅", "✓",
]

# 反向關鍵字:出現這些就算原本有 hint 也跳過
_REJECT_HINTS = [
    "不行", "不可以", "做不到", "沒空", "沒辦法", "不接", "我不",
    "問", "為什麼", "怎麼",  # 提問不是認領
]

ACCEPT_WINDOW_HOURS = 24


def _has_accept_hint(text: str) -> bool:
    if not text:
        return False
    if len(text) > 60:        # 太長不像短確認
        return False
    low = text.lower()
    if any(rh in text for rh in _REJECT_HINTS):
        return False
    return any(h.lower() in low for h in _ACCEPT_HINTS)


async def try_link_acceptance(message_id: UUID) -> None:
    """背景任務:看這則訊息是否認領了某個自動建出的任務。"""
    if not settings.ANTHROPIC_API_KEY:
        return

    async with AsyncSessionLocal() as db:
        msg = (await db.execute(select(Message).where(Message.id == message_id))).scalar_one_or_none()
        if msg is None or msg.user_id is None:
            return
        content = (msg.content or "").strip()
        if not _has_accept_hint(content):
            return
        sender_id = msg.user_id
        channel_id = msg.channel_id
        msg_created_at = msg.created_at

        # 找最近 24hr 內 task_create_auto activities,sender 是 assignee
        cutoff = datetime.now(timezone.utc) - timedelta(hours=ACCEPT_WINDOW_HOURS)
        acts = (await db.execute(
            select(AgentActivity).where(
                AgentActivity.activity_type == "task_create_auto",
                AgentActivity.created_at >= cutoff,
                AgentActivity.created_at <= msg_created_at,   # 只看派工早於確認
            ).order_by(AgentActivity.created_at.desc())
        )).scalars().all()

        candidates = []
        for a in acts:
            extra = a.extra or {}
            if str(extra.get("channel_id")) != str(channel_id):
                continue
            tid_str = extra.get("task_id")
            if not tid_str:
                continue
            try:
                tid = UUID(tid_str)
            except (ValueError, TypeError):
                continue
            t = (await db.execute(
                select(Task).where(Task.id == tid, Task.deleted_at.is_(None))
            )).scalar_one_or_none()
            if t is None:
                continue
            if t.assignee_id != sender_id:
                continue
            # 跳過已被認領過的(同一筆 task 一次認領就好)
            already = (await db.execute(
                select(AgentActivity).where(
                    AgentActivity.activity_type == "task_accepted",
                ).order_by(AgentActivity.created_at.desc()).limit(50)
            )).scalars().all()
            already_task_ids = {(a.extra or {}).get("task_id") for a in already}
            if str(tid) in already_task_ids:
                continue
            candidates.append((t, a))

        if not candidates:
            return
        sender = (await db.execute(select(User).where(User.id == sender_id))).scalar_one_or_none()
        sender_name = sender.display_name if sender else "(未知)"

    # 短確認 + 含關鍵字 → 直接認領「最新一筆」未認領 candidate(不打 Haiku)
    # candidates 已照 created_at desc 排序 → [0] 是最新
    SHORT_CONFIRM_HINTS = ("OK", "ok", "好", "收到", "沒問題", "了解", "可以", "行", "成", "👌", "✅", "已收")
    if len(content) <= 18 and any(kw in content for kw in SHORT_CONFIRM_HINTS):
        t, a = candidates[0]
        await _record_acceptance(t.id, message_id, sender_id, content, source_act_id=a.id, conf=0.9)
        logger.info("task_accepted (fast-path): user=%s task=%s n_candidates=%d msg=%s",
                    sender_name, t.title[:40], len(candidates), content[:30])
        return

    # 較長/模糊訊息才問 Haiku
    titles = "\n".join(f"  - 任務 {i+1}: 《{t.title}》" for i, (t, _) in enumerate(candidates))
    prompt = (
        f"訊息:「{content}」\n"
        f"傳訊者:{sender_name}\n"
        f"傳訊者最近 24hr 被派的任務:\n{titles}\n\n"
        f"判斷:這則訊息是否表達『接受/承接』這些任務?\n"
        f"嚴格 JSON 回應(不要說明、不要 markdown):\n"
        f'{{"is_acceptance": true/false, "confidence": 0.0-1.0, '
        f'"task_index": 1-based 的任務編號 或 null(若不接受或無法判斷)}}'
    )
    try:
        client = anthropic.AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
        resp = await client.messages.create(
            model=settings.CLAUDE_MODEL_HAIKU,
            max_tokens=120,
            system="你是「任務認領判斷員」。只回嚴格 JSON,沒其他字。",
            messages=[{"role": "user", "content": prompt}],
        )
        raw = "".join(b.text for b in resp.content if b.type == "text")
        m = re.search(r"\{.*\}", raw, flags=re.DOTALL)
        if not m:
            return
        result = json.loads(m.group(0))
        if not result.get("is_acceptance"):
            return
        conf = float(result.get("confidence") or 0.0)
        idx = result.get("task_index")
        if not isinstance(idx, int) or not (1 <= idx <= len(candidates)):
            return
        if conf < 0.65:
            return
        t, a = candidates[idx - 1]
        await _record_acceptance(t.id, message_id, sender_id, content,
                                 source_act_id=a.id, conf=conf)
        logger.info("task_accepted: user=%s task=%s conf=%.2f msg=%s",
                    sender_name, t.title[:40], conf, content[:30])
    except Exception:
        logger.exception("task_acceptance Haiku 判斷失敗")


async def _record_acceptance(task_id: UUID, msg_id: UUID, sender_id: UUID,
                              content: str, source_act_id: UUID, conf: float) -> None:
    async with AsyncSessionLocal() as db:
        db.add(AgentActivity(
            activity_type="task_accepted",
            user_id=sender_id,
            summary=f"認領任務(信心 {conf:.2f}):{content[:60]}",
            success=True,
            extra={
                "task_id": str(task_id),
                "acceptance_msg_id": str(msg_id),
                "source_create_act_id": str(source_act_id),
                "confidence": conf,
            },
        ))
        await db.commit()


# 強引用,避免 fire-and-forget task 被 GC 收掉(常見 pitfall)
_PENDING_TASKS: set = set()


def trigger_acceptance(message_id: UUID) -> None:
    """fire-and-forget background task — 強引用避免 GC。"""
    import asyncio
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        return  # 沒 event loop → 跳過(理論上不會發生)
    task = loop.create_task(try_link_acceptance(message_id))
    _PENDING_TASKS.add(task)
    task.add_done_callback(_PENDING_TASKS.discard)


# ============================================================
# 查 acceptance 狀態(給 Cooper 工具用)
# ============================================================


async def list_assignment_status(channel_id: UUID | None = None,
                                  hours: int = 24) -> list[dict]:
    """列最近 N 小時自動建出的任務 + 認領狀態。

    回傳 list:每筆 {task_id, title, assignee_name, status, accepted, accepted_at, accepted_msg_preview}
    """
    cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
    async with AsyncSessionLocal() as db:
        creates = (await db.execute(
            select(AgentActivity).where(
                AgentActivity.activity_type == "task_create_auto",
                AgentActivity.created_at >= cutoff,
            ).order_by(AgentActivity.created_at.desc())
        )).scalars().all()

        if channel_id:
            creates = [a for a in creates if (a.extra or {}).get("channel_id") == str(channel_id)]

        # 收齊 task_id list
        task_ids = []
        for a in creates:
            tid = (a.extra or {}).get("task_id")
            if tid:
                try:
                    task_ids.append(UUID(tid))
                except (ValueError, TypeError):
                    pass
        if not task_ids:
            return []

        tasks_map = {}
        ts = (await db.execute(select(Task).where(Task.id.in_(task_ids)))).scalars().all()
        for t in ts:
            tasks_map[t.id] = t

        # 認領 activities
        accepts = (await db.execute(
            select(AgentActivity).where(
                AgentActivity.activity_type == "task_accepted",
                AgentActivity.created_at >= cutoff,
            )
        )).scalars().all()
        accept_map = {}
        for a in accepts:
            tid_str = (a.extra or {}).get("task_id")
            if tid_str:
                accept_map[tid_str] = a

        users_needed = set()
        for t in tasks_map.values():
            if t.assignee_id:
                users_needed.add(t.assignee_id)
        users_map = {}
        if users_needed:
            us = (await db.execute(select(User).where(User.id.in_(users_needed)))).scalars().all()
            for u in us:
                users_map[u.id] = u

        result = []
        for a in creates:
            tid_str = (a.extra or {}).get("task_id")
            if not tid_str:
                continue
            try:
                tid = UUID(tid_str)
            except (ValueError, TypeError):
                continue
            t = tasks_map.get(tid)
            if t is None or t.deleted_at:
                continue
            assignee_name = "(未指派)"
            if t.assignee_id:
                u = users_map.get(t.assignee_id)
                if u:
                    assignee_name = u.display_name
            ack = accept_map.get(tid_str)
            result.append({
                "task_id": str(tid),
                "title": t.title,
                "status": t.status,
                "assignee_name": assignee_name,
                "created_at": a.created_at.isoformat(),
                "accepted": ack is not None,
                "accepted_at": ack.created_at.isoformat() if ack else None,
                "accepted_confidence": (ack.extra or {}).get("confidence") if ack else None,
            })
        return result
