#!/usr/bin/env python3
"""
热点雷达 — 检测多个公众号是否同时在追同一话题，实时预警
每 30 分钟执行一次（Hermes cronjob: */30 8-22 * * *）
有热点才推送企微群，静默时不发消息
"""
import json, os, re, sqlite3, shutil
from datetime import datetime, timezone, timedelta
from itertools import combinations
from collections import defaultdict

import requests

CST = timezone(timedelta(hours=8))
DB_PATH = os.path.expanduser("~/project/we-mp-rss-data/db.db")
WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=***"
STATE_FILE = os.path.expanduser("~/project/state/hot_radar_sent.json")
os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)

_env = os.path.expanduser("~/project/.env")
for line in open(_env, encoding="utf-8"):
    line = line.strip()
    if line and not line.startswith("#") and "=" in line:
        k, _, v = line.partition("=")
        k, v = k.strip(), v.strip().strip('"').strip("'")
        if k: os.environ[k] = v

DEEPSEEK_KEY = os.getenv("DEEPSEEK_API_KEY", "")
STOP = {'一个','可以','如何','什么','关于','已经','这些','一下','对于','应该',
        '还是','不会','真的','现在','没有','不要','为什么','来了','注意','公布',
        '解答','最新','通知','重要','关注','刚刚','消息','速看'}

def get_db():
    copy = "/tmp/hot_radar.db"
    shutil.copy2(DB_PATH, copy)
    db = sqlite3.connect(copy)
    db.row_factory = sqlite3.Row
    return db

def get_recent_articles(hours=3):
    db = get_db()
    cutoff = int((datetime.now(CST) - timedelta(hours=hours)).timestamp())
    rows = db.execute("""
        SELECT a.id, a.title, a.url, a.publish_time, f.mp_name as source
        FROM articles a JOIN feeds f ON a.mp_id = f.id
        WHERE a.publish_time >= ?
        ORDER BY a.publish_time DESC
    """, (cutoff,)).fetchall()
    db.close()
    return [dict(r) for r in rows]

def extract_keywords(title):
    words = re.findall(r'[\u4e00-\u9fff]{2,4}', title)
    return [w for w in words if w not in STOP]

def verify_hotspot(pairs):
    if not pairs: return []
    item_list = "\n".join(f"{i+1}. [{p['source']}] {p['title']}" for i, p in enumerate(pairs))
    prompt = f"""以下文章是否在追同一个新闻事件/话题？只回复 JSON：{{"hot": true/false, "topic": "话题名(5字内)", "reason": "1句话判断"}}

{item_list}"""
    resp = requests.post("https://api.deepseek.com/chat_completions", json={
        "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}],
        "temperature": 0.1, "max_tokens": 200,
    }, headers={"Authorization": f"Bearer {DEEPSEEK_KEY}"}, timeout=30)
    if resp.status_code == 200:
        try:
            text = resp.json()["choices"][0]["message"]["content"]
            return [json.loads(text)] if json.loads(text).get("hot") else []
        except: pass
    return []

def push_alert(hotspots):
    lines = ["**🚨 热点预警**\n"]
    for h in hotspots:
        lines.append(f"**{h.get('topic', '热门话题')}**")
        if h.get("reason"): lines.append(f">{h['reason']}")
        for a in h.get("articles", []):
            lines.append(f"> [{a['source']}] [{a['title']}]({a['url']})")
        lines.append("")
    content = "\n".join(lines)
    max_bytes = 4000
    encoded = content.encode("utf-8")
    if len(encoded) > max_bytes:
        truncated = ""
        for ch in content:
            if len((truncated + ch).encode("utf-8")) > max_bytes - 30: break
            truncated += ch
        content = truncated + "\n...(截断)"
    r = requests.post(WEBHOOK, json={"msgtype": "markdown", "markdown": {"content": content}}, timeout=10)
    return r.status_code == 200 and r.json().get("errcode") == 0

def load_sent():
    if os.path.exists(STATE_FILE): return set(json.load(open(STATE_FILE)))
    return set()

def save_sent(ids):
    open(STATE_FILE, "w").write(json.dumps(list(ids)))

if __name__ == "__main__":
    articles = get_recent_articles(3)
    if len(articles) < 2:
        print(f"文章不足（{len(articles)}篇），跳过"); exit(0)
    print(f"检测 {len(articles)} 篇（最近3小时）")
    candidates = []
    for a1, a2 in combinations(articles, 2):
        if a1["source"] == a2["source"]: continue
        kw1 = set(extract_keywords(a1["title"]))
        kw2 = set(extract_keywords(a2["title"]))
        common = kw1 & kw2
        if len(common) >= 2: candidates.append((a1, a2, common))
    if not candidates: print("无候选热点"); exit(0)
    hotspots, sent_ids = [], load_sent()
    for a1, a2, kw in candidates[:10]:
        gid = frozenset([a1["id"], a2["id"]])
        if gid in sent_ids: continue
        result = verify_hotspot([a1, a2])
        if result:
            result[0]["articles"] = [a1, a2]
            hotspots.append(result[0])
            sent_ids.add(gid)
    if hotspots:
        push_alert(hotspots); save_sent(sent_ids); print("热点预警已推送")
    else: print("AI 判断非热点，静默")
