#!/usr/bin/env python3
"""客户 + 活动 分段刷新（避开 MCP OFFSET<=1000 上限）
- 按日期区间切片，段内 OFFSET 翻页（ASC，避免 DESC+日期上界的分页假死）
- 段内拉取数 < 首屏 count 或 >= 950 行 → 自动对半再切（递归，深度<=4）
- 写 tmp → 校验条数下限 → os.replace 替换正式文件
用法: python3 _refresh_cust_act.py [customers|activities|both]
"""
import json, os, re, sys, time, urllib.request
from datetime import date, datetime, timedelta

MCP_URL = "https://project.feishu.cn/mcp_server/v1"
MCP_TOKEN = "m-abfb29e8-3104-434f-9944-8d0bb592f8cd"
DATA_DIR = "/Users/liuxinyuan/Desktop/Hermes输出-工作类/数据"
SALES_PK = "6593cd71471290e3cc6be6e6"
TYPE_CUSTOMER = "65ae1e403c87b152f3365ca6"
TYPE_ACTIVITY = "65ae1e5d44338dbe7c39a29a"
ACT_START = "2026-07-01"
MIN_CUSTOMERS = 1600
MIN_ACTIVITIES = 4000


def mcp_call(args, timeout=120, retries=4):
    payload = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
               "params": {"name": "search_by_mql", "arguments": args}}
    for attempt in range(1, retries + 1):
        try:
            req = urllib.request.Request(
                MCP_URL, data=json.dumps(payload).encode("utf-8"),
                headers={"X-Mcp-Token": MCP_TOKEN, "Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=timeout) as r:
                raw = r.read().decode("utf-8")
        except Exception as e:
            if attempt == retries:
                print(f"    ❌ HTTP {retries}次失败: {e}")
                return None
            time.sleep(3 * attempt)
            continue
        if raw.startswith("data:"):
            raw = raw.split("\n")[0].replace("data: ", "", 1)
        try:
            d = json.loads(raw)
        except Exception:
            print(f"    ❌ JSON解析失败: {raw[:150]}")
            time.sleep(5)
            continue
        if "error" in d:
            print(f"    ❌ MCP error: {json.dumps(d['error'], ensure_ascii=False)[:150]}")
            return None
        if "result" not in d or "content" not in d["result"]:
            print(f"    ❌ 意外响应: {json.dumps(d, ensure_ascii=False)[:150]}")
            time.sleep(5)
            continue
        for c in d["result"]["content"]:
            t = c.get("text", "")
            if not t or "log_id" in t:
                continue
            cleaned = re.sub(r"\nlog_id:.*$", "", t.strip())
            try:
                return json.loads(cleaned)
            except Exception:
                pass
    return None


def parse_items(result):
    items = []
    if not result:
        return items
    for _gid, gitems in (result.get("data") or {}).items():
        if not isinstance(gitems, list):
            continue
        for item in gitems:
            fields = {}
            for f in (item.get("moql_field_list") or []):
                k = f["key"]
                v = f.get("value")
                if isinstance(v, list) and len(v) > 0:
                    if isinstance(v[0], dict) and "label" in v[0]:
                        fields[k] = v[0]["label"]
                        continue
                    v = v[0]
                if v is None:
                    fields[k] = ""
                elif isinstance(v, dict):
                    for shape in ("string_value", "double_value", "long_value",
                                  "key_label_value", "user_value", "user_value_list",
                                  "key_label_value_list"):
                        if shape in v:
                            fields[k] = v[shape]
                            break
                    else:
                        fields[k] = str(v)
                else:
                    fields[k] = str(v)
            items.append(fields)
    return items


def pull_segment(type_key, cols, start, end):
    """拉一段（<1000 行）。返回 (items, header_total)"""
    where = []
    if start:
        where.append(f"start_time >= '{start}'")
    if end:
        where.append(f"start_time < '{end}'")
    mql = f"SELECT {cols} FROM `销售管理`.`{type_key}`"
    if where:
        mql += " WHERE " + " AND ".join(where)
    mql += " ORDER BY start_time ASC"

    items, seen, offset, empty = [], set(), 0, 0
    header_total = 0
    for _page in range(1, 21):  # 20 页 = 1000 行，卡在 OFFSET 上限内
        result = mcp_call({"project_key": SALES_PK, "mql": f"{mql} LIMIT 50 OFFSET {offset}",
                           "session_id": ""})
        if result is None:
            print(f"    ⚠️ 段 {start or '-inf'}~{end or '+inf'} OFFSET={offset} 失败，中断本段")
            return items, header_total, False
        batch = parse_items(result)
        try:
            header_total = result["list"][0]["count"]
        except Exception:
            pass
        if not batch:
            empty += 1
            if empty >= 2:
                break
        else:
            empty = 0
            for it in batch:
                wid = str(it.get("work_item_id", ""))
                if wid and wid in seen:
                    continue
                if wid:
                    seen.add(wid)
                items.append(it)
        offset += 50
        if header_total and offset >= min(header_total, 1000):
            break
        time.sleep(0.25)
    return items, header_total, True


def pull_range(type_key, cols, start, end, depth=0):
    """自动对半再切，直到每段 <1000 行且拉全"""
    items, total, ok = pull_segment(type_key, cols, start, end)
    if not ok:
        return items
    need_split = (total and len(items) < total and len(items) >= 900) or (total and total > 1000)
    if need_split and depth < 4:
        s = datetime.strptime(start, "%Y-%m-%d") if start else None
        e = datetime.strptime(end, "%Y-%m-%d") if end else None
        if s is None:  # 无下界段：用首行日期近似下界
            if items:
                first = min((it.get("start_time", "")[:10] for it in items if it.get("start_time")), default="")
                if first and first < (end or "9999-12-31"):
                    s = datetime.strptime(first, "%Y-%m-%d")
        if s and e and (e - s).days > 1:
            mid = (s + (e - s) / 2).strftime("%Y-%m-%d")
        elif s and e is None:
            mid = (s + (datetime.now() - s) / 2).strftime("%Y-%m-%d")
        else:
            return items
        if mid in (start, end):
            return items
        print(f"    ↩︎ 段 {start or '-inf'}~{end or '+inf'} 未拉全(total={total}, got={len(items)})，切分为 {start}~{mid} + {mid}~{end}")
        return pull_range(type_key, cols, start, mid, depth + 1) + pull_range(type_key, cols, mid, end, depth + 1)
    if total and len(items) < total:
        print(f"    ⚠️ 段 {start or '-inf'}~{end or '+inf'} 拉取 {len(items)} < 总数 {total}（未能再切），保留现有")
    print(f"    段 {start or '-inf'}~{end or '+inf'}: {len(items)} 条" + (f" (总数 {total})" if total else ""))
    return items


def dedupe(chunks_items):
    out, seen = [], set()
    for it in chunks_items:
        wid = str(it.get("work_item_id", ""))
        if wid and wid in seen:
            continue
        if wid:
            seen.add(wid)
        out.append(it)
    return out


def run_customers():
    cols = ("name, field_c3224b, field_5ed7ab, field_c8e80d, "
            "field_17186c, field_6415cf, work_item_id")
    chunks = [("2024-01-01", "2024-06-01"), ("2024-06-01", "2024-07-01"),
              ("2024-07-01", "2025-01-01"), ("2025-01-01", "2026-01-01"),
              ("2026-01-01", None)]
    all_items = []
    for s, e in chunks:
        all_items += pull_range(TYPE_CUSTOMER, cols, s, e)
    items = dedupe(all_items)
    items.sort(key=lambda x: x.get("name", ""))
    return items


def run_activities():
    cols = "name, start_time, field_b99055, field_5f20fc, owner, work_item_id"
    d = datetime.strptime(ACT_START, "%Y-%m-%d").date()
    today = date.today()
    chunks = []
    while d < today:
        nxt = min(d + timedelta(days=7), today)
        chunks.append((d.isoformat(), nxt.isoformat() if nxt < today else None))
        d = nxt
    all_items = []
    for s, e in chunks:
        all_items += pull_range(TYPE_ACTIVITY, cols, s, e)
    items = dedupe(all_items)
    items.sort(key=lambda x: x.get("start_time", ""), reverse=True)
    return items


def main():
    which = sys.argv[1] if len(sys.argv) > 1 else "both"
    now = time.strftime("%Y-%m-%d %H:%M:%S")
    status = {}

    if which in ("customers", "both"):
        print("=" * 60); print("客户刷新（分段 + 自动再切）"); print("=" * 60)
        items = run_customers()
        tmp = os.path.join(DATA_DIR, "mcp_customers.json.tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump({"updated_at": now, "customers": items}, f, ensure_ascii=False, indent=2)
        if len(items) < MIN_CUSTOMERS:
            status["customers"] = f"❌ 仅 {len(items)} 条 (<{MIN_CUSTOMERS})，保留 tmp 不替换"
            print("  🔴 " + status["customers"])
        else:
            os.replace(tmp, os.path.join(DATA_DIR, "mcp_customers.json"))
            status["customers"] = f"✅ {len(items)} 条"
            print(f"  ✅ 客户写入 {len(items)} 条")

    if which in ("activities", "both"):
        print("=" * 60); print("活动刷新（分段 + 自动再切）"); print("=" * 60)
        items = run_activities()
        tmp = os.path.join(DATA_DIR, "daily_activities_mcp.json.tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump({"updated": now, "total": len(items), "items": items}, f, ensure_ascii=False, indent=2)
        if len(items) < MIN_ACTIVITIES:
            status["activities"] = f"❌ 仅 {len(items)} 条 (<{MIN_ACTIVITIES})，保留 tmp 不替换"
            print("  🔴 " + status["activities"])
        else:
            os.replace(tmp, os.path.join(DATA_DIR, "daily_activities_mcp.json"))
            status["activities"] = f"✅ {len(items)} 条"
            print(f"  ✅ 活动写入 {len(items)} 条")

    print("\n汇总:", json.dumps(status, ensure_ascii=False))


if __name__ == "__main__":
    main()
