#!/usr/bin/env python3
"""
销售线索自动回填 v5 — 只写 D/H/I/J 四列
执行频率：每小时 cron 巡检
归属 Agent：小溪 (xiaoxi)

v5 精简版（2026-06-04 陈逸鸫确认）：只写 D/H/I/J，K~V 由 Cursor 公式接管
分工约定见 docs/bot-xiaoxi-contract-v5.md

表格列结构:
  D: 体验节数 (小溪 cron)
  E: 手机号 (销售填 → 小溪读)
  H: 用户ID (小溪 cron)
  I: 注册日期 (小溪 cron)
  J: 下载渠道 (小溪 cron)
"""
import json, requests, os, re, sys, time, psycopg2
from datetime import datetime

SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPTS_DIR)
from phone_encrypt import encrypt_phone

PG_HOST = "bj-postgres-16pob4sg.sql.tencentcdb.com"
PG_PORT = 28591
PG_USER = "ai_member"
PG_DB = "vala_bi"

SPREADSHEET_TOKEN = "NoZqsFi47hIOHEt9j8WcfRtbnug"
SHEET_IDS = {"吴迪": "f975f0", "小龙": "qJF4I", "成都": "qJF4J"}
CRED_DIR = "/root/.openclaw/credentials/xiaoxi"
LOG_FILE = "/var/log/xiaoxi_sales_lead.log"


def log(msg):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    with open(LOG_FILE, "a") as f:
        f.write(line + "\n")


def get_pg_password():
    secrets_path = os.path.join(SCRIPTS_DIR, "..", "secrets.env")
    with open(secrets_path) as f:
        for line in f:
            if line.startswith("PG_ONLINE_PASSWORD="):
                return line.strip().split("=", 1)[1].strip("'\"")


def get_fs_token():
    with open(os.path.join(CRED_DIR, "config.json")) as f:
        cfg = json.load(f)
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": cfg["apps"][0]["appId"], "app_secret": cfg["apps"][0]["appSecret"]},
        timeout=15
    )
    return resp.json()["tenant_access_token"]


def read_sheet(token, sheet_id):
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{sheet_id}"
    resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=30)
    data = resp.json()
    if data.get("code") != 0:
        raise RuntimeError(f"读取Sheet失败: {data}")
    return data["data"]["valueRange"]["values"]


def put_values(token, sheet_id, range_str, values, retries=3):
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values"
    body = {"valueRange": {"range": f"{sheet_id}!{range_str}", "values": values}}
    for attempt in range(retries):
        resp = requests.put(url, headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        }, json=body, timeout=30)
        result = resp.json()
        code = result.get("code", -1)
        if code == 0:
            return result
        if code == 90217:
            wait = 2 ** attempt
            log(f"  限流 {sheet_id}!{range_str}, 等待{wait}s重试...")
            time.sleep(wait)
            continue
        log(f"  写入失败 {sheet_id}!{range_str}: {result}")
        return result
    return result


def encrypt_phone_local(phone):
    phone = str(phone).strip()
    if "." in phone:
        parts = phone.split(".")
        if parts[1] in ("0", "00"):
            phone = parts[0]
    if re.match(r"^1\d{10}$", phone):
        return encrypt_phone(phone)
    return None


def process_sheet(token, cur, sheet_name, sheet_id):
    log(f"\n--- [{sheet_name}] {sheet_id} ---")

    rows = read_sheet(token, sheet_id)
    if len(rows) <= 2:
        log(f"  [{sheet_name}] 无数据行，跳过")
        return {"processed": 0, "matched": 0}

    log(f"  [{sheet_name}] 读取到 {len(rows) - 2} 行数据")

    # v5: 只检查 D(3), H(7), I(8), J(9) 是否为空
    auto_cols = [3, 7, 8, 9]
    pending = []
    for idx, row in enumerate(rows[2:], start=3):
        if len(row) < 5:
            continue
        phone = str(row[4]).strip() if len(row) > 4 and row[4] else ""
        if not phone:
            continue
        for ci in auto_cols:
            val = str(row[ci]).strip() if len(row) > ci and row[ci] else ""
            if not val:
                pending.append({"row_idx": idx, "phone": phone})
                break

    log(f"  [{sheet_name}] 待处理: {len(pending)} 行")
    if not pending:
        return {"processed": 0, "matched": 0}

    # 手机号 → account_id
    phones_raw = list(set(r["phone"] for r in pending))
    valid_phones = [(p, encrypt_phone_local(p)) for p in phones_raw if encrypt_phone_local(p)]
    enc_list = list(set(m[1] for m in valid_phones))

    enc_to_aid = {}
    for i in range(0, len(enc_list), 500):
        batch = enc_list[i:i + 500]
        ph = ",".join(["%s"] * len(batch))
        cur.execute(
            f"SELECT id, tel_encrypt FROM bi_vala_app_account WHERE tel_encrypt IN ({ph}) AND status=1 AND deleted_at IS NULL",
            batch
        )
        for aid, tel_enc in cur.fetchall():
            if tel_enc not in enc_to_aid:
                enc_to_aid[tel_enc] = aid

    phone_to_aid = {}
    for phone, enc in valid_phones:
        if enc in enc_to_aid:
            phone_to_aid[phone] = enc_to_aid[enc]

    log(f"  [{sheet_name}] 手机号匹配: {len(phone_to_aid)}/{len(valid_phones)}")

    matched_aids = list(set(phone_to_aid.values()))

    if not matched_aids:
        for r in pending:
            put_values(token, sheet_id, f"H{r['row_idx']}:H{r['row_idx']}", [["未注册"]])
        log(f"  [{sheet_name}] 全部未匹配，已标记 {len(pending)} 行")
        return {"processed": len(pending), "matched": 0}

    # 账号信息: I(注册日期), J(下载渠道)
    cur.execute(
        f"SELECT id, created_at, download_channel FROM bi_vala_app_account WHERE id IN ({','.join(['%s']*len(matched_aids))}) AND status=1 AND deleted_at IS NULL",
        matched_aids
    )
    account_info = {}
    for aid, created_at, channel in cur.fetchall():
        account_info[aid] = {
            "created_at": created_at.strftime("%Y-%m-%d") if created_at else "",
            "download_channel": channel or ""
        }

    # 体验课完成节数: D列
    cur.execute(
        f"SELECT a.id, COUNT(*) FROM bi_vala_app_account a INNER JOIN bi_vala_app_character c ON a.id=c.account_id AND c.deleted_at IS NULL INNER JOIN bi_user_course_detail ucd ON c.id=ucd.user_id AND ucd.deleted_at IS NULL WHERE a.id IN ({','.join(['%s']*len(matched_aids))}) AND a.status=1 AND a.deleted_at IS NULL AND ucd.expire_time IS NULL GROUP BY a.id",
        matched_aids
    )
    trial_count = {r[0]: r[1] for r in cur.fetchall()}

    # 组装回填数据（v5: 只 D/H/I/J）
    results = []
    for r in pending:
        phone = r["phone"]
        aid = phone_to_aid.get(phone)
        row_num = r["row_idx"]

        if not aid:
            results.append({"row": row_num, "H": "未注册", "I": "", "J": "", "D": ""})
            continue

        info = account_info.get(aid, {})
        trials = trial_count.get(aid, 0)
        results.append({
            "row": row_num,
            "D": str(trials) if trials else "0",
            "H": str(aid),
            "I": info.get("created_at", ""),
            "J": info.get("download_channel", ""),
        })

    results.sort(key=lambda x: x["row"])
    log(f"  [{sheet_name}] 准备回填 {len(results)} 行")

    # 按连续行分组写入
    groups = []
    cur_grp = []
    for r in results:
        if not cur_grp or r["row"] == cur_grp[-1]["row"] + 1:
            cur_grp.append(r)
        else:
            groups.append(cur_grp)
            cur_grp = [r]
    if cur_grp:
        groups.append(cur_grp)

    for gi, g in enumerate(groups):
        sr, er = g[0]["row"], g[-1]["row"]
        # D列
        d_vals = [[r.get("D", "")] for r in g]
        put_values(token, sheet_id, f"D{sr}:D{er}", d_vals)
        # H~J 整块写
        hij_vals = [[r.get("H", ""), r.get("I", ""), r.get("J", "")] for r in g]
        put_values(token, sheet_id, f"H{sr}:J{er}", hij_vals)
        if gi % 5 == 4:
            time.sleep(0.5)

    matched_count = sum(1 for r in results if r["H"] != "未注册")
    log(f"  [{sheet_name}] 回填完成: {len(results)} 行, 匹配 {matched_count}, 未注册 {len(results) - matched_count}")
    return {"processed": len(results), "matched": matched_count}


def main():
    log("=" * 50)
    log("销售线索自动回填 v5 启动（只写 D/H/I/J）")

    try:
        token = get_fs_token()
        conn = psycopg2.connect(
            host=PG_HOST, port=PG_PORT, user=PG_USER,
            password=get_pg_password(), dbname=PG_DB, connect_timeout=30
        )
        cur = conn.cursor()

        total_processed = 0
        total_matched = 0

        for sheet_name, sheet_id in SHEET_IDS.items():
            r = process_sheet(token, cur, sheet_name, sheet_id)
            total_processed += r["processed"]
            total_matched += r["matched"]

        cur.close()
        conn.close()

        log(f"\n全部完成: 处理 {total_processed} 行, 匹配 {total_matched}, 未注册 {total_processed - total_matched}")
        return 0

    except Exception as e:
        log(f"ERROR: {e}")
        import traceback
        traceback.print_exc()
        return 1


if __name__ == "__main__":
    sys.exit(main())
