#!/usr/bin/env python3
"""WeChat webhook receiver v3 — per-user session context via Hermes session ID."""

import os, sys, json, logging, threading, queue, time
from http.server import HTTPServer, BaseHTTPRequestHandler

HERMES_API = "http://192.168.1.246:8646/v1/chat/completions"
HERMES_KEY = "hermes123"
PORT = 5804

ARTICLE_PROCESSOR_API = "http://192.168.1.16:5810/process"
KB_API = "http://192.168.1.246:9090/api/articles"

WECHAT_BOT_TOKEN = os.environ.get("WECHAT_BOT_TOKEN", "mowechat_fixed_token_001")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("/home/hmo/AgentsMeeting/gateway/linux/logs/webhook.log"),
        logging.StreamHandler()
    ]
)
log = logging.getLogger("wc-webhook")

# ── 消息队列（串行处理，防止并发打爆 Hermes）──────────────
_msg_queue = queue.Queue()


def _hermes_worker():
    """单线程 worker，从队列取消息逐条发给 Hermes。"""
    while True:
        sender, sender_id, text = _msg_queue.get()
        try:
            _do_forward(sender, sender_id, text)
        except Exception as e:
            log.error(f"Worker error: {e}")
        finally:
            _msg_queue.task_done()
        # 每条消息之间稍作间隔，避免 LLM API 限流
        time.sleep(2)


def _do_forward(sender, sender_id, text):
    """调用 Hermes API 并回复微信（带 X-Hermes-Session-Id 实现 per-user 上下文）。"""
    session_id = f"wechat-{sender_id}"
    payload = json.dumps({
        "model": "nova-4",
        "messages": [
            {"role": "user", "content": f"[微信消息] 来自 {sender}({sender_id}): {text}"}
        ]
    }).encode()

    try:
        import urllib.request as ureq
        handler = ureq.ProxyHandler({})
        opener = ureq.build_opener(handler)
        req = ureq.Request(HERMES_API, data=payload,
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {HERMES_KEY}",
                "X-Hermes-Session-Id": session_id,
            })
        resp = opener.open(req, timeout=600)
        resp_data = json.loads(resp.read())
        reply = resp_data.get('choices', [{}])[0].get('message', {}).get('content', '')
        log.info(f"Hermes OK [{sender[:8]}], reply: {reply[:60]}")

        if reply and sender:
            WebhookHandler._send_wechat_static(sender, reply)
    except Exception as e:
        log.error(f"Hermes error [{sender[:8]}]: {e}")


# 启动 worker 线程
_worker_thread = threading.Thread(target=_hermes_worker, daemon=True)
_worker_thread.start()


# ── 文章抓取 + 入库 ──────────────────────────────────────
def _fetch_article(url):
    """调用 article_processor 抓取文章全文。"""
    import urllib.request as ureq
    payload = json.dumps({"url": url}).encode()
    req = ureq.Request(ARTICLE_PROCESSOR_API, data=payload,
        headers={"Content-Type": "application/json"})
    with ureq.urlopen(req, timeout=300) as resp:
        return json.loads(resp.read().decode())


def _register_article(data):
    """注册文章到知识库 DB。"""
    import urllib.request as ureq
    payload = json.dumps(data, ensure_ascii=False).encode("utf-8")
    req = ureq.Request(KB_API, data=payload,
        headers={"Content-Type": "application/json"}, method="POST")
    try:
        with ureq.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read().decode())
    except Exception as e:
        log.error(f"KB register error: {e}")
        return None


def _is_login_expired(text, result):
    """检测抓取结果是否为登录失效/验证码页面。
    只有内容极短(<200字)且包含明确验证码关键词时才判定为风控。"""
    if not text:
        return True
    # 内容足够长 = 正常文章，不可能是验证码页
    if len(text) >= 200:
        return False
    # 内容太短但没有验证码关键词 = 可能只是短文章，不误判
    captcha_keywords = ["环境异常", "安全验证", "滑动验证", "扫码验证", "操作频繁", "请完成安全验证"]
    if any(k in text for k in captcha_keywords):
        return True
    return False


def _process_article(sender, sender_id, content):
    """处理转发文章：抓取 → 入库 → 交 Hermes 回复。"""
    import re as _re
    import urllib.request as ureq

    # 提取 URL
    url_match = _re.search(r'"url":"(https://mp\.weixin\.qq\.com/[^"]+)"', content)
    if not url_match:
        log.warning(f"No URL found in urlLink content")
        _do_forward(sender, sender_id, f"[分享链接] {content}")
        return
    article_url = url_match.group(1).replace("&amp;", "&")
    log.info(f"Fetching article: {article_url[:80]}...")

    # 抓取
    try:
        result = _fetch_article(article_url)
        title = result.get("title", "unknown")
        text = result.get("content", "")
        word_count = result.get("word_count", 0)
        log.info(f"Fetched: {title[:40]} ({word_count} chars)")

        # 检测风控/登录失效
        if _is_login_expired(text, result):
            log.warning(f"Login expired or captcha detected for {article_url[:60]}")
            hermes_msg = (
                f"[微信消息] 来自 {sender}({sender_id}): [分享链接] {content}\n\n"
                f"[系统] 抓取这篇文章时检测到微信读者登录态已失效（页面返回验证码/环境异常）。"
                f"这不是文章本身的问题，而是 article_processor 的 Chrome 微信读者 session 过期了。"
                f"需要老莫在 Windows 上重新运行 wechat_fetcher.py --login 扫码登录后，抓取服务才能恢复。"
                f"请告诉老莫这个情况，让他知道需要重新扫码。"
            )
            _do_forward(sender, sender_id, hermes_msg)
            return

    except Exception as e:
        log.error(f"Fetch failed: {e}")
        _do_forward(sender, sender_id, f"[分享链接] {content}")
        return

    # 入库
    kb_data = {
        "url": article_url,
        "title": title,
        "source": "user_sent",
        "platform": "wechat",
        "lifecycle": "draft",
        "full_text": text[:5000] if text else "",
    }
    kb_result = _register_article(kb_data)
    kb_id = kb_result.get("id") if kb_result else None
    log.info(f"KB registered: id={kb_id}")

    # 交 Hermes 回复（带入库结果）
    kb_status = f"已入库(id={kb_id})" if kb_id else "入库失败"
    hermes_msg = f"[微信消息] 来自 {sender}({sender_id}): [分享链接] {content}\n\n[系统] 文章已自动抓取并{kb_status}，全文 {word_count} 字。"
    _do_forward(sender, sender_id, hermes_msg)


class WebhookHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_type = self.headers.get('Content-Type', '')
        content_length = int(self.headers.get('Content-Length', 0))
        
        # Read the body
        body = self.rfile.read(content_length)
        
        # Parse multipart/form-data
        msg_type = 'unknown'
        content = ''
        source_raw = '{}'
        is_system = '0'
        
        if 'multipart/form-data' in content_type:
            # Manual multipart parsing
            boundary = content_type.split('boundary=')[1].strip()
            if boundary.startswith('"') and boundary.endswith('"'):
                boundary = boundary[1:-1]
            
            parts = body.split(b'--' + boundary.encode())
            for part in parts:
                if b'Content-Disposition' not in part:
                    continue
                
                # Parse headers
                header_end = part.find(b'\r\n\r\n')
                if header_end < 0:
                    continue
                part_headers = part[:header_end].decode('utf-8', errors='replace')
                part_body = part[header_end + 4:]
                
                # Get field name
                name_start = part_headers.find('name="')
                if name_start < 0:
                    continue
                name_start += 6
                name_end = part_headers.find('"', name_start)
                field_name = part_headers[name_start:name_end]
                
                # Trim trailing \r\n--
                if part_body.endswith(b'\r\n'):
                    part_body = part_body[:-2]
                if part_body.endswith(b'--'):
                    part_body = part_body[:-2]
                if part_body.endswith(b'\r\n'):
                    part_body = part_body[:-2]
                
                if field_name == 'type':
                    msg_type = part_body.decode('utf-8', errors='replace')
                elif field_name == 'content':
                    content = part_body.decode('utf-8', errors='replace')
                elif field_name == 'source':
                    source_raw = part_body.decode('utf-8', errors='replace')
                elif field_name == 'isSystemEvent':
                    is_system = part_body.decode('utf-8', errors='replace')
        else:
            # Try as regular form or JSON
            try:
                data = json.loads(body)
                msg_type = data.get('type', 'unknown')
                content = data.get('content', '')
                source_raw = data.get('source', '{}')
            except:
                pass
        
        # Parse source
        try:
            source = json.loads(source_raw) if isinstance(source_raw, str) else source_raw
        except:
            source = {}
        
        # Extract sender info
        sender_name = "unknown"
        sender_id = "unknown"
        if isinstance(source, dict):
            from_data = source.get('from', {})
            if isinstance(from_data, dict):
                payload = from_data.get('payload', {})
                sender_name = payload.get('name', 'unknown')
                sender_id = payload.get('id', 'unknown')
        
        # Skip system events
        if is_system == '1':
            log.info(f"System event: {msg_type}")
            self._respond(200, {"status": "ok"})
            return
        
        log.info(f"From: {sender_name} ({sender_id}), Type: {msg_type}")
        
        if msg_type == 'text':
            log.info(f"Text: {content}")
            self._forward_to_hermes(sender_name, sender_id, content)
        elif msg_type == 'urlLink':
            log.info(f"URL: {content}")
            # 转发文章：抓取 → 入库 → 交 Hermes 回复
            _process_article(sender_name, sender_id, content)
        elif msg_type == 'file':
            log.info(f"File received, length={len(body)}")
        else:
            log.info(f"Other: {msg_type}")
        
        self._respond(200, {"status": "ok"})
    
    def _forward_to_hermes(self, sender, sender_id, text):
        """把消息放入队列，由 worker 串行处理。"""
        _msg_queue.put((sender, sender_id, text))
        log.info(f"Queued for Hermes (queue size: {_msg_queue.qsize()})")

    @staticmethod
    def _send_wechat_static(to_name, text):
        """Send message back to WeChat user via bot API (static, for worker)."""
        import urllib.request as ureq
        token = WECHAT_BOT_TOKEN
        api = f"http://localhost:3001/webhook/msg/v2?token={token}"
        data = json.dumps({"to": to_name, "data": {"type": "text", "content": text}}).encode()
        try:
            handler = ureq.ProxyHandler({})
            opener = ureq.build_opener(handler)
            req = ureq.Request(api, data=data,
                headers={"Content-Type": "application/json"})
            resp = opener.open(req, timeout=10)
            log.info(f"WeChat send OK: {resp.status}")
        except Exception as e:
            log.error(f"WeChat send error: {e}")

    def _send_wechat(self, to_name, text):
        """Instance method wrapper."""
        self._send_wechat_static(to_name, text)
    
    def _respond(self, code, data):
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())
    
    def log_message(self, format, *args):
        pass


def main():
    server = HTTPServer(('0.0.0.0', PORT), WebhookHandler)
    log.info(f"Webhook receiver on :{PORT}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()


if __name__ == "__main__":
    main()
