"""
會議處理流水線(階段 4):音訊 → 逐字稿 → 摘要 → 待辦事項 → 推 Coba 訊息。

執行流程:
  1. POST /api/meetings 把音訊存到磁碟,建 Meeting record(status=uploaded)
  2. 觸發背景任務 process_meeting()
  3. status=transcribing → 呼叫 faster-whisper 跑本地語音辨識
  4. status=summarizing → 把逐字稿丟給 Claude(Sonnet)產出摘要 + 決策 + 待辦
  5. status=completed → Coba 在團隊頻道發一則「會議摘要」訊息

Whisper 模型:
  - 預設 small(244MB,中文準度夠,CPU 約 1x realtime)
  - 第一次跑會自動下載到 ~/.cache/huggingface/

成本:Whisper 本地跑免費,Claude 摘要 ~$0.01-0.05/場
"""

import asyncio
import json
import logging
import re
from pathlib import Path
from uuid import UUID

import anthropic
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from app.config import settings
from app.core.ws_manager import manager as ws_manager
from app.database import AsyncSessionLocal
from app.models.channel import Channel, Message
from app.models.meeting import (
    Meeting,
    MeetingActionItem,
    MeetingDecision,
    MeetingSummary,
    MeetingTranscript,
)
from app.models.user import User


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


WHISPER_MODEL_SIZE = "small"   # tiny / base / small / medium / large-v3
_whisper_model = None


def _get_whisper_model():
    """延遲載入 Whisper 模型(第一次會下載 ~244MB)。"""
    global _whisper_model
    if _whisper_model is None:
        from faster_whisper import WhisperModel
        logger.info("載入 Whisper(%s)模型,第一次會下載...", WHISPER_MODEL_SIZE)
        # CPU 模式,int8 量化(較省記憶體)
        _whisper_model = WhisperModel(WHISPER_MODEL_SIZE, device="cpu", compute_type="int8")
        logger.info("Whisper 模型 ready")
    return _whisper_model


def _transcribe_sync(audio_path: str) -> tuple[list[dict], float]:
    """同步在 thread 裡跑 Whisper,回傳 (segments, total_duration_seconds)。"""
    model = _get_whisper_model()
    segments_iter, info = model.transcribe(
        audio_path,
        language="zh",      # 強制中文(混雜英文也 OK)
        beam_size=5,
        vad_filter=True,    # 跳過靜音段
    )
    segments = []
    for s in segments_iter:
        segments.append({
            "start": s.start,
            "end": s.end,
            "text": s.text.strip(),
            "confidence": getattr(s, "avg_logprob", None),
        })
    return segments, info.duration


SUMMARY_SYSTEM_PROMPT = """你是會議紀錄助手。我會給你一場會議的逐字稿,請你產出嚴格 JSON 格式的摘要。

【輸出格式】
{
  "executive_summary": "三句話以內,把整場會議的核心結論濃縮給沒參加的人看",
  "key_topics": ["主題1", "主題2", ...],
  "decisions": [
    {"decision": "決定了什麼", "context": "為什麼這樣決定(可省略)"}
  ],
  "action_items": [
    {"description": "誰要做什麼", "suggested_assignee_name": "建議負責人姓名(若逐字稿有提到)", "suggested_due_date": "YYYY-MM-DD 或 null"}
  ]
}

【規則】
- 用繁體中文
- decisions 跟 action_items 沒有就給空陣列
- 嚴格 JSON 格式,不要 markdown 程式碼框,不要解釋
"""


def _parse_summary_json(raw: str) -> dict:
    """容錯解析 Claude 回傳的 JSON。"""
    raw = raw.strip()
    # 抓第一個 { ... } 區段
    m = re.search(r"\{[\s\S]*\}", raw)
    if not m:
        return {}
    try:
        return json.loads(m.group(0))
    except json.JSONDecodeError:
        return {}


async def _broadcast_status(meeting_id: UUID, status: str) -> None:
    """推送會議狀態變化(讓前端可以顯示「處理中...」)。"""
    await ws_manager.broadcast({
        "type": "meeting_status",
        "meeting_id": str(meeting_id),
        "status": status,
    })


async def _post_summary_to_chat(db, meeting: Meeting) -> None:
    """Coba 在團隊頻道發一則特殊訊息:會議摘要 + action items 按鈕。

    用 message_type='meeting_summary',content 是 JSON 字串(前端特別 render)。
    """
    # 找預設「團隊對話」頻道
    ch = (await db.execute(
        select(Channel).where(
            Channel.name == "團隊對話",
            Channel.is_default.is_(True),
            Channel.deleted_at.is_(None),
        )
    )).scalar_one_or_none()
    if ch is None:
        # 沒「團隊對話」就發到任意 default 公開頻道
        ch = (await db.execute(
            select(Channel).where(
                Channel.is_default.is_(True),
                Channel.deleted_at.is_(None),
            ).limit(1)
        )).scalar_one_or_none()
    if ch is None:
        logger.warning("找不到任何預設頻道,跳過摘要訊息")
        return

    # 顯式抓 summary/decisions/action_items(避免 ORM session cache 問題)
    summary = (await db.execute(
        select(MeetingSummary).where(MeetingSummary.meeting_id == meeting.id)
    )).scalar_one_or_none()
    decisions = list((await db.execute(
        select(MeetingDecision)
        .where(MeetingDecision.meeting_id == meeting.id)
        .order_by(MeetingDecision.order_index)
    )).scalars().all())
    action_items = list((await db.execute(
        select(MeetingActionItem)
        .where(MeetingActionItem.meeting_id == meeting.id)
        .order_by(MeetingActionItem.order_index)
    )).scalars().all())

    payload = {
        "meeting_id": str(meeting.id),
        "title": meeting.title,
        "summary": summary.executive_summary if summary else "",
        "key_topics": summary.key_topics if summary else [],
        "decisions": [{"id": str(d.id), "decision": d.decision, "context": d.context} for d in decisions],
        "action_items": [
            {
                "id": str(a.id),
                "description": a.description,
                "suggested_assignee_id": str(a.suggested_assignee_id) if a.suggested_assignee_id else None,
                "suggested_due_date": a.suggested_due_date,
                "converted_task_id": str(a.converted_task_id) if a.converted_task_id else None,
            }
            for a in action_items
        ],
    }

    msg = Message(
        channel_id=ch.id,
        user_id=None,                       # NULL = 庫柏
        content=json.dumps(payload, ensure_ascii=False),
        message_type="meeting_summary",
    )
    db.add(msg)
    await db.commit()
    await db.refresh(msg)

    meeting.summary_message_id = msg.id
    await db.commit()

    # 廣播 message_new
    from app.api.messages import (
        _build_read_by,
        _get_channel_read_states,
        _serialize_message,
        _ws_safe,
    )
    msg_full = (await db.execute(
        select(Message)
        .options(selectinload(Message.reactions))
        .where(Message.id == msg.id)
    )).scalar_one()
    member_reads = await _get_channel_read_states(ch.id, db)
    serialized = _serialize_message(msg_full, _build_read_by(msg_full, member_reads))
    await ws_manager.broadcast(_ws_safe({
        "type": "message_new",
        "channel_id": str(ch.id),
        "message": serialized,
    }))


async def process_meeting(meeting_id: UUID) -> None:
    """背景任務:把上傳的會議從頭處理到尾。"""
    async with AsyncSessionLocal() as db:
        meeting = (await db.execute(
            select(Meeting).where(Meeting.id == meeting_id)
        )).scalar_one_or_none()
        if meeting is None or not meeting.audio_path:
            return

        try:
            # ── Step 1: Whisper 轉逐字稿 ──
            meeting.status = "transcribing"
            await db.commit()
            await _broadcast_status(meeting_id, "transcribing")
            logger.info("開始轉逐字稿:%s", meeting.audio_path)

            segments, duration = await asyncio.to_thread(_transcribe_sync, meeting.audio_path)
            meeting.audio_duration_seconds = duration
            for i, seg in enumerate(segments):
                db.add(MeetingTranscript(
                    meeting_id=meeting_id,
                    speaker_label="speaker_0",   # 階段 5 升級 WhisperX 才有 diarization
                    start_time=seg["start"],
                    end_time=seg["end"],
                    content=seg["text"],
                    confidence=seg["confidence"],
                    order_index=i,
                ))
            await db.commit()
            logger.info("逐字稿完成:%d 段 / %.1f 秒", len(segments), duration)

            # 沒有任何文字就跳過摘要
            full_text = "\n".join(s["text"] for s in segments).strip()
            if not full_text:
                meeting.status = "failed"
                meeting.error_message = "音訊辨識不到任何內容(可能是靜音檔)"
                await db.commit()
                await _broadcast_status(meeting_id, "failed")
                return

            # ── Step 2: Claude 摘要 ──
            meeting.status = "summarizing"
            await db.commit()
            await _broadcast_status(meeting_id, "summarizing")

            if not settings.ANTHROPIC_API_KEY:
                meeting.status = "failed"
                meeting.error_message = "ANTHROPIC_API_KEY 未設定"
                await db.commit()
                await _broadcast_status(meeting_id, "failed")
                return

            client = anthropic.AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
            user_prompt = f"以下是會議逐字稿,請產出 JSON 摘要:\n\n{full_text}"
            response = await client.messages.create(
                model=settings.CLAUDE_MODEL_SONNET,
                max_tokens=2048,
                system=SUMMARY_SYSTEM_PROMPT,
                messages=[{"role": "user", "content": user_prompt}],
            )
            raw = "".join(b.text for b in response.content if b.type == "text")
            data = _parse_summary_json(raw)

            # 存摘要
            summary = MeetingSummary(
                meeting_id=meeting_id,
                executive_summary=data.get("executive_summary", "(無法產生摘要)"),
                key_topics=data.get("key_topics") or [],
                generated_by=settings.CLAUDE_MODEL_SONNET,
                input_tokens=response.usage.input_tokens,
                output_tokens=response.usage.output_tokens,
            )
            db.add(summary)

            # 存決策
            for i, d in enumerate(data.get("decisions") or []):
                if isinstance(d, dict) and d.get("decision"):
                    db.add(MeetingDecision(
                        meeting_id=meeting_id,
                        decision=d["decision"],
                        context=d.get("context"),
                        order_index=i,
                    ))

            # 存待辦(嘗試把 suggested_assignee_name 對應到團隊使用者)
            users = list((await db.execute(
                select(User).where(User.deleted_at.is_(None), User.is_active.is_(True))
            )).scalars().all())
            user_by_name = {u.display_name.lower(): u.id for u in users}

            for i, a in enumerate(data.get("action_items") or []):
                if not isinstance(a, dict) or not a.get("description"):
                    continue
                assignee_id = None
                name_hint = (a.get("suggested_assignee_name") or "").strip().lower()
                if name_hint and name_hint in user_by_name:
                    assignee_id = user_by_name[name_hint]
                due = a.get("suggested_due_date") or None
                if due and not re.match(r"^\d{4}-\d{2}-\d{2}$", str(due)):
                    due = None   # 格式不對直接清掉
                db.add(MeetingActionItem(
                    meeting_id=meeting_id,
                    description=a["description"],
                    suggested_assignee_id=assignee_id,
                    suggested_due_date=due,
                    order_index=i,
                ))

            meeting.status = "completed"
            await db.commit()
            await _broadcast_status(meeting_id, "completed")

            # ── Step 2.5(階段 8):把 action_items 自動轉成 Task ──
            await _convert_action_items_to_tasks(meeting_id, db)

            cost = (response.usage.input_tokens / 1_000_000) * 3.0 + (response.usage.output_tokens / 1_000_000) * 15.0
            logger.info(
                "會議處理完成 id=%s tokens=%d/%d cost=US$%.4f",
                str(meeting_id)[:8],
                response.usage.input_tokens,
                response.usage.output_tokens,
                cost,
            )

            # ── Step 3: Coba 在團隊頻道發摘要訊息 ──
            await _post_summary_to_chat(db, meeting)

        except Exception as e:
            logger.exception("會議處理失敗")
            meeting.status = "failed"
            meeting.error_message = f"{type(e).__name__}: {e}"
            await db.commit()
            await _broadcast_status(meeting_id, "failed")


def trigger_meeting_processing(meeting_id: UUID) -> None:
    """同步入口,啟背景任務不 block HTTP 上傳回應。"""
    asyncio.create_task(process_meeting(meeting_id))


# ============================================================
# 階段 8:會議結束後,把 action_items 自動轉成 Task
# ============================================================


async def _convert_action_items_to_tasks(meeting_id: UUID, db) -> None:
    """讀 meeting 的 action_items,每筆建一個 Task(若還沒對應)。"""
    from app.models.task import Task
    from app.models.meeting import MeetingActionItem
    from app.models.agent_activity import AgentActivity

    items = list((await db.execute(
        select(MeetingActionItem).where(
            MeetingActionItem.meeting_id == meeting_id,
            MeetingActionItem.converted_task_id.is_(None),
        )
    )).scalars().all())
    if not items:
        return

    meeting = (await db.execute(
        select(Meeting).where(Meeting.id == meeting_id)
    )).scalar_one_or_none()
    creator_id = meeting.uploaded_by if meeting else None
    if creator_id is None:
        # 沒上傳者就跳過(不該發生,但保險)
        logger.warning("meeting %s 沒 uploaded_by,action_item 無法建 task", meeting_id)
        return

    created_titles = []
    for ai in items:
        # 建 Task
        try:
            due = ai.suggested_due_date
            if isinstance(due, str):
                from datetime import datetime
                try:
                    due = datetime.strptime(due, "%Y-%m-%d").date()
                except ValueError:
                    due = None
            t = Task(
                title=ai.description[:200],
                description=f"從會議「{meeting.title}」自動產出 (action_item)",
                priority="medium",
                due_date=due,
                assignee_id=ai.suggested_assignee_id,
                created_by=creator_id,
                status="todo",
            )
            db.add(t)
            await db.flush()
            ai.converted_task_id = t.id
            created_titles.append(t.title)
            db.add(AgentActivity(
                activity_type="task_create_auto",
                user_id=creator_id,
                summary=f"會議自動轉任務:{t.title}",
                extra={
                    "task_id": str(t.id),
                    "meeting_id": str(meeting_id),
                    "via": "meeting_action_item",
                },
            ))
        except Exception:
            logger.exception("action_item 轉 task 失敗")
    await db.commit()

    if created_titles:
        logger.info(
            "meeting %s 自動建 %d 個任務:%s",
            str(meeting_id)[:8], len(created_titles), created_titles[:3]
        )
