#!/usr/bin/env python3
"""
usage_collector_sensenova.py — SenseNova Token Plan 用量采集（CDP 浏览器自动化）
==========================================================
连接 Chrome CDP proxy (localhost:3456)，打开 SenseNova 控制台，
提取 3 个模型的 5 小时窗口剩余量，保存到 usage_stats_sensenova.json。

控制台（2026-07-23 实测）：
  sensenova-6.7-flash-lite  99.73%剩余
  sensenova-u1-fast         100%剩余
  deepseek-v4-flash         99.80%剩余
公测期免费，各模型 5 小时窗口独立计数，账户下所有 Key 共享配额。

调用方式: python usage_collector_sensenova.py
"""
import json, os, sys, time, urllib.request, re
from pathlib import Path
from datetime import datetime, timezone

CDP = "http://localhost:3456"
CONSOLE_URL = "https://platform.sensenova.cn/console"

SCRIPT_DIR = Path(__file__).resolve().parent
GATEWAY_DIR = SCRIPT_DIR.parent
TEMP_DIR = GATEWAY_DIR / "temp"
os.makedirs(str(TEMP_DIR), exist_ok=True)

OUTPUT_FILE = TEMP_DIR / "usage_stats_sensenova.json"
PROVIDER = "sensenova"

# 控制台三个模型 → pseudo-key 映射（/api/keys 展示用）
MODEL_KEYS = [
    ("sensenova-6.7-flash-lite", "key8", "SenseNova 6.7 Flash-Lite"),
    ("sensenova-u1-fast", "key9", "SenseNova U1-Fast"),
    ("deepseek-v4-flash", "key10", "SenseNova DeepSeek-V4-Flash"),
]


def cdp_get(path, timeout=10):
    try:
        with urllib.request.urlopen(f"{CDP}{path}", timeout=timeout) as r:
            return json.loads(r.read().decode())
    except Exception as e:
        return {"error": str(e)}


def cdp_post(path, body="", timeout=15):
    try:
        data = body.encode("utf-8") if isinstance(body, str) else body
        req = urllib.request.Request(f"{CDP}{path}", data=data, method="POST")
        req.add_header("Content-Type", "text/plain")
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return r.read().decode()
    except Exception as e:
        return f"ERROR: {e}"


def cdp_eval(target_id, js, timeout=10):
    return cdp_post(f"/eval?target={target_id}", js, timeout)


def get_page_text(target_id):
    raw = cdp_eval(target_id, "document.body ? document.body.innerText : ''")
    try:
        parsed = json.loads(raw) if isinstance(raw, str) else raw
        return parsed.get("value", "") if isinstance(parsed, dict) else str(raw)
    except (json.JSONDecodeError, AttributeError):
        return str(raw)


def poll_page_ready(target_id, keywords=None, timeout=25, interval=1):
    for attempt in range(int(timeout / interval)):
        text = get_page_text(target_id)
        if keywords and all(k in text for k in keywords):
            print(f"[sensenova] ready (attempt {attempt + 1})")
            return text
        time.sleep(interval)
    print(f"[sensenova] poll timeout, using current state")
    return get_page_text(target_id)


def extract_model_quotas(text):
    """从控制台文本提取各模型剩余百分比。
    实测格式（2026-07-23）：'sensenova-6.7-flash-lite\\n\\n99.73%剩余\\n\\nsensenova-u1-fast\\n\\n100%剩余...'
    注意图表图例区有模型名连写（sensenova-6.7-flash-litesensenova-u1-fast...），
    必须锚定"模型名+换行+数字%剩余"结构，否则会误匹配图例位置抓到别家数值。
    返回 {model_name: remaining_pct}"""
    quotas = {}
    for model, _, _ in MODEL_KEYS:
        m = re.search(re.escape(model) + r'\s*\n+\s*(\d+(?:\.\d+)?)\s*%\s*剩余', text)
        if m:
            quotas[model] = float(m.group(1))
    return quotas


def ensure_cdp_proxy():
    result = cdp_get("/targets", timeout=3)
    if isinstance(result, list):
        return True
    print("[sensenova] CDP proxy not running, attempting to start...")
    import subprocess
    ps_script = r"D:\F\NewI\opencode\daily-workspace\.opencode\scripts\start-cdp-proxy.ps1"
    try:
        proc = subprocess.run(
            ["powershell", "-ExecutionPolicy", "Bypass", "-File", ps_script],
            capture_output=True, text=True, timeout=20)
        if proc.returncode == 0 and isinstance(cdp_get("/targets", timeout=5), list):
            print("[sensenova] CDP proxy started OK")
            return True
    except Exception as e:
        print(f"[sensenova] CDP auto-start failed: {e}")
    return False


def find_or_open_console_tab():
    targets = cdp_get("/targets")
    if not isinstance(targets, list):
        return None
    for t in targets:
        if "platform.sensenova.cn" in (t.get("url") or ""):
            return t.get("targetId") or t.get("id")
    # open new background tab
    r = cdp_get(f"/new?url={CONSOLE_URL}", timeout=30)
    if isinstance(r, dict) and r.get("targetId"):
        return r["targetId"]
    print(f"[sensenova] open tab failed: {r}")
    return None


def main():
    print("[sensenova] Starting collection...")
    if not ensure_cdp_proxy():
        print("[sensenova] ERROR: CDP proxy unavailable")
        sys.exit(1)

    tid = find_or_open_console_tab()
    if not tid:
        print("[sensenova] ERROR: cannot get console tab")
        sys.exit(1)

    text = poll_page_ready(tid, keywords=["剩余"])
    quotas = extract_model_quotas(text)
    print(f"[sensenova] quotas: {quotas}")

    now_iso = datetime.now(timezone.utc).isoformat()
    accounts = []
    for model, key_id, label in MODEL_KEYS:
        remaining = quotas.get(model)
        if remaining is None:
            accounts.append({
                "key_id": key_id, "label": label, "provider": PROVIDER,
                "last_update_iso": now_iso,
                "error": "quota not found on console page",
                "rolling": {"usage_percent": None, "reset_in_sec": None, "status": "unknown"},
            })
            continue
        usage = round(100.0 - remaining, 2)
        status = "ok" if usage < 80 else ("warn" if usage < 95 else "rate-limited")
        accounts.append({
            "key_id": key_id, "label": label, "provider": PROVIDER,
            "last_update_iso": now_iso,
            "error": None,
            "rolling": {"usage_percent": usage, "reset_in_sec": 5 * 3600, "status": status},
            "monthly": None, "weekly": None,
            "remaining_percent": remaining,
            "note": "公测免费·5小时窗口·账户所有Key共享",
        })

    out = {
        "source": "usage_collector_sensenova.py (CDP scrape of platform.sensenova.cn/console)",
        "collected_at": now_iso,
        "accounts": accounts,
    }
    with open(str(OUTPUT_FILE), "w", encoding="utf-8") as f:
        json.dump(out, f, ensure_ascii=False, indent=2)
    print(f"[sensenova] saved → {OUTPUT_FILE} ({sum(1 for a in accounts if not a.get('error'))}/3 models)")


if __name__ == "__main__":
    main()
