#!/usr/bin/env python3
"""
销售表行课状态同步 — 从销售表读UID → 查DB行课 → 回填体验节数
执行频率：每30分钟 cron 巡检
归属 Agent：小溪 (xiaoxi)

流程：
  1. 读取小龙(qJF4I)和吴迪(f975f0)销售表，提取有UID的行
  2. 查DB获取每个用户完成的课时数（唯一chapter_id）
  3. 回填销售表D列（体验节数）

过程数据 J/N/R/V/Z 由 COUNTIFS 公式自动读取销售表D列，无需脚本写入。
"""
import json, requests, os, sys, psycopg2
from datetime import datetime
from collections import defaultdict

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"

BOT_TOKEN = "NoZqsFi47hIOHEt9j8WcfRtbnug"
CRED_DIR = "/root/.openclaw/credentials/xiaoxi"

SALES_SHEETS = {
    "小龙": "qJF4I",
    "吴迪": "f975f0",
}

LOG_FILE = "/var/log/xiaoxi_sales_lesson_sync.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, range_str):
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{BOT_TOKEN}/values/{sheet_id}!{range_str}"
    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):
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{BOT_TOKEN}/values"
    body = {"valueRange": {"range": f"{sheet_id}!{range_str}", "values": values}}
    resp = requests.put(url, headers={
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }, json=body, timeout=30)
    return resp.json()

def parse_date(date_str):
    """解析 'X月Y日' → (month, day)"""
    date_str = str(date_str).strip()
    if '月' in date_str and '日' in date_str:
        parts = date_str.replace('月', ' ').replace('日', '').split()
        try:
            return int(parts[0]), int(parts[1])
        except (ValueError, IndexError):
            pass
    return None, None

def batch_in(cur, sql_tpl, params, chunk=500):
    results = []
    for i in range(0, len(params), chunk):
        batch = params[i:i+chunk]
        ph = ",".join(["%s"] * len(batch))
        cur.execute(sql_tpl % ph, batch)
        results.extend(cur.fetchall())
    return results


def main():
    log("=" * 50)
    log("销售表行课状态同步 启动")

    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()

        # ── Step 1: 读取销售表，提取UID（优先H列，H空则E列手机号匹配） ──
        all_users = []  # [{sales, name, uid, month, row_num}]
        phone_rows = []  # [{sales, sheet_id, name, phone, month, row_num}]  H列为空但有手机号的行

        for sales_name, sheet_id in SALES_SHEETS.items():
            rows = read_sheet(token, sheet_id, "A1:K2000")
            for idx, row in enumerate(rows[2:], start=3):  # skip header + legend
                if not row or len(row) < 8:
                    continue
                uid_str = str(row[7]).strip() if len(row) > 7 and row[7] else ''
                phone_str = str(row[4]).strip() if len(row) > 4 and row[4] else ''
                date_str = str(row[2]).strip() if len(row) > 2 and row[2] else ''
                name = str(row[1]).strip() if len(row) > 1 and row[1] else ''

                month, day = parse_date(date_str)
                if month is None:
                    continue

                # 优先用H列UID
                if uid_str and uid_str not in ('', 'None', '未注册'):
                    try:
                        uid = int(float(uid_str))
                        if uid > 0:
                            all_users.append({
                                "sales": sales_name,
                                "sheet_id": sheet_id,
                                "name": name,
                                "uid": uid,
                                "month": month,
                                "row_num": idx,
                            })
                            continue
                    except (ValueError, TypeError):
                        pass

                # H列无有效UID，尝试E列手机号匹配
                if phone_str and phone_str not in ('', 'None', '-'):
                    # 清洗手机号：去空格、去+86前缀
                    phone_clean = phone_str.strip().replace(' ', '').replace('\t', '')
                    if phone_clean.startswith('+86'):
                        phone_clean = phone_clean[3:]
                    # 验证是否为11位数字手机号
                    if len(phone_clean) == 11 and phone_clean.isdigit() and phone_clean.startswith('1'):
                        phone_rows.append({
                            "sales": sales_name,
                            "sheet_id": sheet_id,
                            "name": name,
                            "phone": phone_clean,
                            "month": month,
                            "row_num": idx,
                        })

        # 手机号匹配 account_id
        phone_to_uid = {}
        if phone_rows:
            phone_enc_map = {}  # {encrypted: phone}
            for pr in phone_rows:
                enc = encrypt_phone(pr["phone"])
                phone_enc_map[enc] = pr["phone"]

            enc_list = list(phone_enc_map.keys())
            rc = batch_in(cur,
                "SELECT id, tel_encrypt FROM bi_vala_app_account WHERE tel_encrypt IN (%s) AND status=1 AND deleted_at IS NULL",
                enc_list
            )
            for aid, tel_enc in rc:
                phone = phone_enc_map.get(tel_enc)
                if phone:
                    phone_to_uid[phone] = aid

            # 将匹配到的加入 all_users
            for pr in phone_rows:
                uid = phone_to_uid.get(pr["phone"])
                if uid:
                    all_users.append({
                        "sales": pr["sales"],
                        "sheet_id": pr["sheet_id"],
                        "name": pr["name"],
                        "uid": uid,
                        "month": pr["month"],
                        "row_num": pr["row_num"],
                    })

        log(f"Step 1: 读取销售表, 有效UID: {len(all_users)} (含手机号匹配: {len(phone_to_uid)})")

        if not all_users:
            log("无有效UID, 退出")
            cur.close()
            conn.close()
            return 0

        # ── Step 2: 查角色映射 ──
        uid_set = list(set(u["uid"] for u in all_users))
        account_chars = defaultdict(list)
        char_to_account = {}

        rc = batch_in(cur,
            "SELECT account_id, id FROM bi_vala_app_character WHERE account_id IN (%s) AND deleted_at IS NULL",
            uid_set
        )
        for aid, cid in rc:
            account_chars[aid].append(cid)
            char_to_account[cid] = aid

        char_ids = list(char_to_account.keys())
        log(f"Step 2: 角色映射, account={len(account_chars)}, char={len(char_ids)}")

        # ── Step 2.5: 查账户信息（注册日期、下载渠道） ──
        uid_info = {}  # uid → {created_at, download_channel}
        rc = batch_in(cur,
            "SELECT id, created_at, download_channel FROM bi_vala_app_account WHERE id IN (%s) AND status=1 AND deleted_at IS NULL",
            uid_set
        )
        for aid, created_at, download_channel in rc:
            uid_info[aid] = {
                "created_at": str(created_at)[:10] if created_at else "",
                "download_channel": download_channel or ""
            }

        # ── Step 3: 查课时完成记录（唯一chapter_id） ──
        char_chapters = defaultdict(set)  # char_id → set of chapter_ids

        for tbl_idx in range(8):
            table = f"bi_user_chapter_play_record_{tbl_idx}"
            try:
                cur.execute(
                    f"SELECT user_id, chapter_id FROM {table} WHERE play_status=1 AND deleted_at IS NULL AND user_id = ANY(%s)",
                    (char_ids,)
                )
                for uid, ch_id in cur.fetchall():
                    char_chapters[uid].add(ch_id)
            except Exception as e:
                log(f"  警告 {table}: {e}")

        # 汇总每个 account 的完成课时数
        uid_lesson_count = {}  # uid → unique chapter count
        for uid in uid_set:
            chars = account_chars.get(uid, [])
            all_chapters = set()
            for cid in chars:
                all_chapters.update(char_chapters.get(cid, set()))
            uid_lesson_count[uid] = len(all_chapters)

        log(f"Step 3: 课时统计完成, 有记录用户: {sum(1 for v in uid_lesson_count.values() if v > 0)}")

        # ── Step 4: 回填销售表 D/H/I/J 列 ──
        # D=体验节数, H=UID(手机号匹配到的回填), I=注册日期, J=下载渠道
        for sales_name, sheet_id in SALES_SHEETS.items():
            # 读 D/H/I/J 四列
            d_existing = read_sheet(token, sheet_id, "D1:D2000")
            h_existing = read_sheet(token, sheet_id, "H1:H2000")
            i_existing = read_sheet(token, sheet_id, "I1:I2000")
            j_existing = read_sheet(token, sheet_id, "J1:J2000")

            new_d, new_h, new_i, new_j = [], [], [], []
            d_changed, h_changed, i_changed, j_changed = 0, 0, 0, 0

            for idx in range(len(d_existing)):
                row_num = idx + 1

                # 保留前2行
                if idx < 2:
                    new_d.append(d_existing[idx] if idx < len(d_existing) else [])
                    new_h.append(h_existing[idx] if idx < len(h_existing) else [])
                    new_i.append(i_existing[idx] if idx < len(i_existing) else [])
                    new_j.append(j_existing[idx] if idx < len(j_existing) else [])
                    continue

                uid_for_row = None
                for u in all_users:
                    if u["sheet_id"] == sheet_id and u["row_num"] == row_num:
                        uid_for_row = u["uid"]
                        break

                if uid_for_row is not None:
                    # D列：体验节数（0留空，>5封顶5）
                    count = uid_lesson_count.get(uid_for_row, 0)
                    if count == 0:
                        d_val = ''  # 0留空
                    elif count > 5:
                        d_val = 5   # 封顶5
                    else:
                        d_val = count
                    old_d = str(d_existing[idx][0]).strip() if idx < len(d_existing) and d_existing[idx] else ''
                    if old_d != str(d_val):
                        d_changed += 1
                    new_d.append([d_val])

                    # H列：UID（手机号匹配到的回填，已有UID不覆盖）
                    old_h = str(h_existing[idx][0]).strip() if idx < len(h_existing) and h_existing[idx] else ''
                    if not old_h or old_h in ('', 'None', '未注册'):
                        new_h.append([uid_for_row])
                        h_changed += 1
                    else:
                        new_h.append(h_existing[idx] if idx < len(h_existing) else [])

                    # I列：注册日期
                    info = uid_info.get(uid_for_row, {})
                    new_i_val = info.get("created_at", "")
                    old_i = str(i_existing[idx][0]).strip() if idx < len(i_existing) and i_existing[idx] else ''
                    if old_i != new_i_val:
                        i_changed += 1
                    new_i.append([new_i_val])

                    # J列：下载渠道
                    new_j_val = info.get("download_channel", "")
                    old_j = str(j_existing[idx][0]).strip() if idx < len(j_existing) and j_existing[idx] else ''
                    if old_j != new_j_val:
                        j_changed += 1
                    new_j.append([new_j_val])
                else:
                    new_d.append(d_existing[idx] if idx < len(d_existing) else [])
                    new_h.append(h_existing[idx] if idx < len(h_existing) else [])
                    new_i.append(i_existing[idx] if idx < len(i_existing) else [])
                    new_j.append(j_existing[idx] if idx < len(j_existing) else [])

            # 写入
            for col_letter, col_name, new_vals, changed in [
                ("D", "体验节数", new_d, d_changed),
                ("H", "UID", new_h, h_changed),
                ("I", "注册日期", new_i, i_changed),
                ("J", "下载渠道", new_j, j_changed),
            ]:
                if changed > 0:
                    rng = f"{col_letter}1:{col_letter}2000"
                    r = put_values(token, sheet_id, rng, new_vals)
                    if r.get("code") != 0:
                        log(f"  写入{col_name}列失败 {sheet_id}: {r}")
                    else:
                        log(f"  {sales_name} {col_name}列更新 {changed} 行")
                else:
                    log(f"  {sales_name} {col_name}列无变化")

        log(f"Step 4: D/H/I/J列回填完成")

        cur.close()
        conn.close()
        log("完成")
        return 0

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


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