#!/usr/bin/env python3
# 并行版回放识别：async playwright，一个浏览器并发开多页渲染回放，识别"购买功能"。
# 通吃所有游戏商。供 scanner 并行扫描用。
import asyncio, os, time
from client import api
from playwright.async_api import async_playwright

BUY_KEYS = ["购买功能", "购买奖金游戏", "购买免费", "购买游戏", "购买",
            "Buy Feature", "Bonus Buy", "Buy Free", "Ante Bet"]
FREE_KEYS = ["免费旋转", "免费游戏", "Free Spin", "Free Game"]
READY_KEYS = ["旋转", "赢奖", "无中奖", "购买", "赌注", "投注大小", "免费", "中奖"]

# 并发页数：内存小的服务器调小(2-3)，本机可大(6-8)。可用环境变量覆盖。
CONCURRENCY = int(os.environ.get("REPLAY_CONCURRENCY", "5"))
# 是否用系统Chrome(Mac=chrome)；服务器装playwright自带chromium则留空
CHROME_CHANNEL = os.environ.get("REPLAY_CHANNEL", "chrome") or None


async def _all_text(page):
    out = []
    for f in list(page.frames):
        try:
            out.append(await f.evaluate("() => document.body ? document.body.innerText : ''") or "")
        except Exception:
            pass
    return "\n".join(out)


async def _wait_rendered(page, max_wait=30):
    """等回放内容渲染稳定再读：文字出现关键词且长度连续2次不再增长才算完成。
    比单纯看关键词可靠(高并发下PG页渲染慢，避免读到半成品漏掉'购买')。"""
    t0 = time.time(); text = ""; last_len = -1; stable = 0
    while time.time() - t0 < max_wait:
        await page.wait_for_timeout(700)
        text = await _all_text(page)
        has_content = sum(k in text for k in READY_KEYS) >= 2 and len(text) > 60
        if has_content and len(text) == last_len:
            stable += 1
            if stable >= 2:        # 连续2次长度不变 = 渲染完成
                break
        else:
            stable = 0
        last_len = len(text)
    return text


def _analyze(text, bet_id):
    hit = [k for k in BUY_KEYS if k in text]
    return {"betId": bet_id, "bought": bool(hit),
            "has_free_spin": any(k in text for k in FREE_KEYS),
            "buy_markers": hit}


async def _check_one(ctx, sem, bet_id):
    async with sem:
        try:
            url = await asyncio.to_thread(
                lambda: api(f"/betManage/userBet/queryOrderInfo/{bet_id}", method="GET")["data"])
        except Exception as e:
            return {"betId": bet_id, "error": f"queryOrderInfo: {e}"}
        page = await ctx.new_page()
        # 只要文字，屏蔽图片/媒体/字体，大幅减轻渲染负担、提速
        async def _block(route):
            if route.request.resource_type in ("image", "media", "font"):
                await route.abort()
            else:
                await route.continue_()
        try:
            await page.route("**/*", _block)
        except Exception:
            pass
        try:
            try:
                await page.goto(url, wait_until="load", timeout=60000)
            except Exception:
                pass
            text = await _wait_rendered(page)
            return _analyze(text, bet_id)
        except Exception as e:
            return {"betId": bet_id, "error": str(e)}
        finally:
            await page.close()


async def check_many(bet_ids, headless=True, concurrency=None):
    """并发识别多个注单是否购买功能。返回 {betId: result}"""
    conc = concurrency or CONCURRENCY
    sem = asyncio.Semaphore(conc)
    async with async_playwright() as p:
        b = await p.chromium.launch(headless=headless, channel=CHROME_CHANNEL)
        ctx = await b.new_context(locale="zh-CN")
        try:
            results = await asyncio.gather(*[_check_one(ctx, sem, bid) for bid in bet_ids])
        finally:
            await b.close()
    return {r["betId"]: r for r in results}


def check_many_sync(bet_ids, headless=True, concurrency=None):
    return asyncio.run(check_many(bet_ids, headless, concurrency))
