#!/usr/bin/env python3
"""
wechat_qr_notifier.py — 微信登录态监控 & 智能通知
================================================
功能:
  1. 检测 session 年龄: 如果已登录超过 40 小时，通过微信发送预警
  2. 检测掉线: 如果微信已登出，通过 XMPP 发送二维码通知
  3. 智能去重: 同一轮预警/掉线不重复通知

部署: crontab 每 30 分钟执行
  */30 * * * * cd /home/hmo/AgentsMeeting/gateway/scripts && /usr/bin/python3 wechat_qr_notifier.py >> ../logs/qr_notifier.log 2>&1
"""

import json, os, re, subprocess, sys, urllib.request, urllib.parse
from datetime import datetime, timedelta

GATEWAY_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMP_DIR = os.path.join(GATEWAY_DIR, "temp")
STATE_FILE = os.path.join(TEMP_DIR, "wechat_qr_state.json")

LOGIN_URL = "http://localhost:3001/login?token=mowechat_fixed_token_001"
DOCKER_CONTAINER = "wxBotWebhook"
WECHAT_BOT_API = "http://localhost:3001/webhook/msg/v2"
WECHAT_BOT_TOKEN = "mowechat_fixed_token_001"
XMPP_FROM = "mohe@yoin.fun"
XMPP_TO = "hmo@yoin.fun"
WEBHOOK_LOG = os.path.join(GATEWAY_DIR, "linux", "logs", "webhook.log")
DASHBOARD_URL = "http://192.168.1.246:5803"

NOTIFY_INTERVAL_MIN = 240      # 掉线通知: 同一轮最多每 4 小时提醒
AGING_WARN_HOURS = 40          # 累计登录 40 小时后发送微信预警
AGING_WARN_INTERVAL_H = 8      # 预警间隔: 每 8 小时提醒一次
QR_IMG_API = "https://api.qrserver.com/v1/create-qr-code/?size=500x500&margin=10&data="


def log(msg):
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] {msg}")


def load_state():
    os.makedirs(TEMP_DIR, exist_ok=True)
    try:
        with open(STATE_FILE, "r") as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}


def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, ensure_ascii=False)


def is_logged_out():
    """检查最近有没掉线事件。"""
    try:
        result = subprocess.run(
            ["docker", "logs", DOCKER_CONTAINER, "--since", "30m"],
            capture_output=True, text=True, timeout=10
        )
        logs = result.stdout + result.stderr
        if "system_event_logout" in logs:
            return True
        if "扫码以下二维码以登录" in logs[-5000:]:
            return True
    except Exception as e:
        log(f"ERROR checking docker logs: {e}")
    return False


def check_session_age():
    """从 webhook log 分析 session 年龄。
    返回: (is_online, session_age_hours, last_ok_time_str)
    """
    try:
        r = subprocess.run(["tail", "-500", WEBHOOK_LOG],
                          capture_output=True, text=True, timeout=5)
        lines = r.stdout.strip().split("\n") if r.stdout else []

        last_ok = None
        last_err = None

        for line in reversed(lines):
            ts_match = re.match(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line)
            ts = ts_match.group(1) if ts_match else None
            if "WeChat send OK" in line and ts and not last_ok:
                last_ok = ts
            elif "WeChat send error" in line and ts and not last_err:
                last_err = ts
            if last_ok and last_err:
                break

        # Determine current state
        is_online = False
        session_age = 0

        if last_ok:
            ok_dt = datetime.strptime(last_ok, "%Y-%m-%d %H:%M:%S")
            session_age = round((datetime.now() - ok_dt).total_seconds() / 3600, 1)

        if last_err and last_ok:
            err_dt = datetime.strptime(last_err, "%Y-%m-%d %H:%M:%S")
            ok_dt = datetime.strptime(last_ok, "%Y-%m-%d %H:%M:%S")
            is_online = ok_dt > err_dt
        elif last_ok and not last_err:
            is_online = True  # No errors seen, assume online
        elif last_err and not last_ok:
            is_online = False

        return is_online, session_age, last_ok

    except Exception as e:
        log(f"ERROR reading webhook log: {e}")
        return False, 0, None


def fetch_qr_url():
    """从登录页面提取微信登录二维码链接。"""
    try:
        with urllib.request.urlopen(LOGIN_URL, timeout=10) as r:
            html = r.read().decode("utf-8", errors="replace")
        m = re.search(r'qrcode\.makeCode\("([^"]+)"\)', html)
        if m:
            return m.group(1)
        m = re.search(r'(https://login\.weixin\.qq\.com/l/[^\s"\'<>]+)', html)
        if m:
            return m.group(1)
    except Exception as e:
        log(f"ERROR fetching QR page: {e}")
    return None


def send_wechat(to_name, text):
    """通过 docker wechatbot-webhook API 发送微信消息。"""
    try:
        url = f"{WECHAT_BOT_API}?token={WECHAT_BOT_TOKEN}"
        payload = json.dumps({"to": to_name, "content": text}).encode("utf-8")
        req = urllib.request.Request(url, data=payload,
            headers={"Content-Type": "application/json"})
        with urllib.request.urlopen(req, timeout=10) as resp:
            if resp.status == 200:
                log(f"WeChat message sent to {to_name}")
                return True
            else:
                log(f"WeChat send failed: HTTP {resp.status}")
                return False
    except Exception as e:
        log(f"WeChat send error: {e}")
        return False


def send_xmpp(qr_url):
    """通过 ejabberdctl 发送 XMPP 消息（用于掉线时，微信不可用）。"""
    qr_img_url = QR_IMG_API + urllib.parse.quote(qr_url, safe="")
    body = (
        f"\U0001f514 莫荷微信掉线了，需要重新扫码登录\n\n"
        f"\U0001f449 打开 Dashboard 健康检查页面扫码：\n"
        f"{DASHBOARD_URL}\n\n"
        f"\U0001f4f1 或用手机扫这个二维码：\n"
        f"{qr_img_url}\n\n"
        f"\u23f0 二维码约 5 分钟过期，请及时扫码"
    )
    body = body.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    stanza = f"<message type='chat'><body>{body}</body></message>"

    try:
        result = subprocess.run(
            ["docker", "exec", "ejabberd", "ejabberdctl", "send_stanza",
             XMPP_FROM, XMPP_TO, stanza],
            capture_output=True, text=True, timeout=15
        )
        if result.returncode == 0:
            log(f"XMPP notification sent to {XMPP_TO}")
            return True
        else:
            log(f"XMPP send failed: {result.stderr}")
            return False
    except Exception as e:
        log(f"XMPP send error: {e}")
        return False


def main():
    now = datetime.now()
    state = load_state()

    # ── 场景 1: 已登录但 session 年龄过大 → 微信预警 ──
    is_online, session_age, last_ok = check_session_age()
    log(f"Session: online={is_online}, age={session_age}h, last_ok={last_ok}")

    if is_online and session_age >= AGING_WARN_HOURS:
        last_aging_warn = state.get("last_aging_warn", "")
        should_warn = True
        if last_aging_warn:
            try:
                last_warn_dt = datetime.fromisoformat(last_aging_warn)
                if (now - last_warn_dt).total_seconds() / 3600 < AGING_WARN_INTERVAL_H:
                    should_warn = False
            except ValueError:
                pass

        if should_warn:
            user_name = "hmo"  # WeChat contact name
            msg = (
                f"\u26a0\ufe0f 莫荷微信 session 已运行 {session_age:.0f} 小时\n\n"
                f"\U0001f4e2 预计未来几小时可能掉线\n"
                f"\U0001f449 请到 Dashboard 健康页面提前准备扫码\n"
                f"{DASHBOARD_URL}"
            )
            if send_wechat(user_name, msg):
                state["last_aging_warn"] = now.isoformat()
                state["aging_warn_hours"] = session_age
                save_state(state)
                log(f"Aging warning sent ({session_age:.0f}h)")
                return

    # ── 场景 2: 已掉线 → XMPP 通知（微信不可用） ──
    if not is_online and (is_logged_out() or session_age > 1):
        log("WeChat bridge appears to be logged out")

        qr_url = fetch_qr_url()
        if not qr_url:
            log("ERROR: could not extract QR code URL")
            return

        last_notify = state.get("last_notify", "")
        last_qr = state.get("last_qr", "")

        should_notify = True
        if last_notify:
            try:
                last_dt = datetime.fromisoformat(last_notify)
                if (now - last_dt).total_seconds() / 60 < NOTIFY_INTERVAL_MIN:
                    should_notify = False
            except ValueError:
                pass

        if should_notify:
            if send_xmpp(qr_url):
                state["last_notify"] = now.isoformat()
                state["last_qr"] = qr_url
                save_state(state)
                log("Logout XMPP notification sent")
        else:
            log(f"Already notified recently, skipping")
        return

    # ── 场景 3: 在线且无需预警 ──
    if is_online and session_age < AGING_WARN_HOURS:
        log(f"wechat bridge OK, session age {session_age:.0f}h")
    elif is_online:
        log(f"session aging ({session_age:.0f}h) but already warned recently")
    else:
        log("not online but no recent logout detected - possibly idle")


if __name__ == "__main__":
    main()
