"""微信(ClawBot)入站长轮询启动器(§8.7;从 app.py lifespan 析出,2026-07-23)。

每个 active 绑定一条 getupdates 长轮询;收到消息 → 跑用户常驻「微信」task →
回复发回(渠道无关核心在 web/runs.py::run_channel_conversation)。
蓝绿双实例用 PG advisory lock 选主,单实例恒拿锁行为不变。
"""
from __future__ import annotations

import asyncio
from typing import Optional
from uuid import UUID

from .runs import run_channel_conversation


def start_wechat_inbound(app) -> tuple[Optional[asyncio.Task], asyncio.Event]:
    """ClawBot 入站启用时起 leader 选举 task;未启用返 (None, stop_event)。

    stop_event:关停时先 set 再 cancel task(leader 循环与 keepalive 都在等它)。
    """
    from core.wechat.service import clawbot_enabled

    stop = asyncio.Event()
    if not clawbot_enabled():
        return None, stop

    from core.wechat.inbound import run_inbound_manager

    async def _run_wechat_message(uid: UUID, text: str, attachments=None) -> str:
        """微信(ClawBot)入站一条消息 → 跑用户常驻「微信」task → 取回复。

        attachments:已下载解密的入站附件(core.wechat.ilink.InboundAttachment,att.data 已回填)。
        建/复用 task、落盘附件、抢 run 锁、跑 agent 全在渠道无关核心
        `run_channel_conversation` 里(企业微信回调走同一核心,channel='wecom')。
        """
        return await run_channel_conversation(app, uid, text, attachments, channel="wechat")

    async def _wechat_leader() -> None:
        """ClawBot 入站长轮询单实例互斥(蓝绿 B 档,RUN.md)。

        getupdates 长轮询是消费型的:蓝绿并存期两实例同时拉会抢消息 / 同一条
        入站双跑 agent。用 PG advisory lock 选主:持锁实例才跑 inbound manager,
        另一实例每 15s 重试;持锁连接断(DB 重启 / 网络抖 = 锁已自动释放)→
        停管理器重新竞争。单实例部署恒拿到锁,行为与之前完全一致。
        """
        from core.storage import get_engine
        engine = get_engine()
        # pg_try_advisory_lock(int4, int4) 的固定 key:('zc'=0x7A63, 1=wechat-inbound)
        lock_sql = "SELECT pg_try_advisory_lock(31331, 1)"

        def _acquire():
            conn = engine.connect()
            # 从连接池摘出独占:advisory lock 是 DBAPI 连接级的,不 detach 的话
            # close() 只是还池,锁挂在池化连接上既不释放也没人持有
            conn.detach()
            try:
                if conn.exec_driver_sql(lock_sql).scalar():
                    return conn
                conn.close()
                return None
            except Exception:
                conn.close()
                raise

        async def _wait(seconds: float) -> None:
            try:
                await asyncio.wait_for(stop.wait(), timeout=seconds)
            except asyncio.TimeoutError:
                pass

        while not stop.is_set():
            conn = None
            mgr = None
            try:
                conn = await asyncio.to_thread(_acquire)
                if conn is None:
                    await _wait(15)
                    continue
                print("[wechat] inbound leader lock acquired, manager starting")
                mgr = asyncio.create_task(
                    run_inbound_manager(_run_wechat_message, stop),
                    name="wechat-inbound",
                )
                # 30s keepalive ping 锁连接;ping 挂 = 连接断 = 锁已丢 → 重选
                while not stop.is_set() and not mgr.done():
                    await _wait(30)
                    if stop.is_set() or mgr.done():
                        break
                    try:
                        await asyncio.to_thread(
                            lambda: conn.exec_driver_sql("SELECT 1").scalar()
                        )
                    except Exception as e:
                        print(f"[wechat] leader lock lost "
                              f"({type(e).__name__}), re-electing")
                        break
            except asyncio.CancelledError:
                raise
            except Exception as e:
                print(f"[wechat] leader loop error: {type(e).__name__}: {e}")
                await _wait(15)
            finally:
                if mgr is not None and not mgr.done():
                    mgr.cancel()
                    try:
                        await mgr
                    except (asyncio.CancelledError, Exception):
                        pass
                if conn is not None:
                    try:
                        conn.close()
                    except Exception:
                        pass

    task = asyncio.create_task(_wechat_leader(), name="wechat-leader")
    print("[wechat] ClawBot inbound enabled (PG advisory lock leader election)")
    return task, stop
