#!/usr/bin/env python3
"""顺丰物流监控 + 飞书弹窗推送
每轮：① MCP拉最新订单 ② 查顺丰轨迹 ③ diff状态 ④ 按销售员飞书卡片推送
"""
import json, os, sys, time, urllib.request, urllib.parse, uuid, subprocess

ROOT = os.path.expanduser('~/Desktop/Hermes输出-工作类')
DATA = os.path.join(ROOT, '数据')
STATE = os.path.expanduser('~/.hermes/scripts/sf_watchdog_state.json')

SF_PARTNER_ID = "BRSWK5CQ08HV"
SF_SECRET = "V7JDT0azGX93pnIXRyZe9zvlzY6fk1rC"
SF_TOKEN_URL = "https://sfapi.sf-express.com/oauth2/accessToken"
SF_API_URL = "https://bspgw.sf-express.com/std/service"

FEISHU_APP_ID = "cli_aac5af01fd625bdd"
FEISHU_APP_SECRET = "FxAZsdQWVRuPOIK8byWDccwNTan7AadN"

# ── 顺丰 API ──
def sf_get_token():
    body = urllib.parse.urlencode({"partnerID": SF_PARTNER_ID, "secret": SF_SECRET, "grantType": "password"}).encode()
    req = urllib.request.Request(SF_TOKEN_URL, data=body, headers={"Content-Type": "application/x-www-form-urlencoded"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        result = json.loads(resp.read().decode())
    if result.get("apiResultCode") != "A1000":
        raise Exception(f"SF token failed: {result}")
    return result["accessToken"]

def sf_query_routes(tracking_numbers, access_token, check_phone=None):
    msg = {"language": "zh-CN", "trackingType": 1, "trackingNumber": tracking_numbers, "methodType": 1}
    if check_phone: msg["checkPhoneNo"] = check_phone
    body = urllib.parse.urlencode({
        "partnerID": SF_PARTNER_ID, "requestID": str(uuid.uuid4()).replace("-", ""),
        "serviceCode": "EXP_RECE_SEARCH_ROUTES", "timestamp": str(int(time.time())),
        "accessToken": access_token, "msgData": json.dumps(msg, ensure_ascii=False)
    }).encode()
    req = urllib.request.Request(SF_API_URL, data=body, headers={"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        result = json.loads(resp.read().decode())
    if result.get("apiResultCode") != "A1000":
        return {"error": result.get("apiErrorMsg", "unknown")}
    return json.loads(result.get("apiResultData", "{}"))

def fine_status(routes, reason=''):
    if not routes: return ('no_data' if reason else 'unknown'), None
    last = routes[-1]; remark = last.get('remark', '')
    if '签收' in remark or '已派送至' in remark: return 'delivered', last
    if '派送' in remark or last.get('opCode') == '30': return 'delivering', last
    if '揽收' in remark or last.get('opCode') == '54': return 'picked_up', last
    return 'in_transit', last

COARSE = {'delivered': ('delivered', '已签收'), 'delivering': ('in_transit', '运输中'),
          'picked_up': ('in_transit', '运输中'), 'in_transit': ('in_transit', '运输中'),
          'no_data': (None, None), 'unknown': (None, None), 'error': (None, None)}

def phone4(o):
    digits = ''.join(c for c in str(o.get('phone') or '') if c.isdigit())
    return digits[-4:] if len(digits) >= 4 else None

# ── 飞书 API ──
_feishu_token = None
def get_feishu_token():
    global _feishu_token
    if _feishu_token: return _feishu_token
    body = json.dumps({'app_id': FEISHU_APP_ID, 'app_secret': FEISHU_APP_SECRET}).encode()
    req = urllib.request.Request('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal',
        data=body, headers={'Content-Type': 'application/json'}, method='POST')
    with urllib.request.urlopen(req, timeout=10) as resp:
        r = json.loads(resp.read())
    if r.get('code') != 0: raise Exception(f"Feishu token failed: {r}")
    _feishu_token = r['tenant_access_token']
    return _feishu_token

def send_feishu_card(email, title, body_text):
    """发送飞书卡片消息给指定邮箱用户"""
    if not email or '@' not in email: return False
    token = get_feishu_token()
    card = json.dumps({
        'config': {'wide_screen_mode': True},
        'header': {'title': {'tag': 'plain_text', 'content': title}},
        'elements': [{'tag': 'div', 'text': {'tag': 'lark_md', 'content': body_text}}]
    })
    msg = json.dumps({'receive_id': email, 'msg_type': 'interactive', 'content': card}).encode()
    req = urllib.request.Request('https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=email',
        data=msg, headers={'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'})
    with urllib.request.urlopen(req, timeout=10) as resp:
        r = json.loads(resp.read())
    return r.get('code') == 0

# ── 主流程 ──
def main():
    # Step 1: MCP 拉取最新订单
    print("[1/4] 拉取 MCP 销售订单...", file=sys.stderr)
    pull = os.path.join(DATA, 'pull_orders.py')
    subprocess.run([sys.executable, pull, '--days', '7'], cwd=DATA, capture_output=True)

    # Step 2: 顺丰轨迹查询
    print("[2/4] 查询顺丰物流轨迹...", file=sys.stderr)
    pipeline = os.path.join(ROOT, '脚本', 'shipment_pipeline.py')
    subprocess.run([sys.executable, pipeline], cwd=ROOT, capture_output=True)

    # 加载数据
    ship_file = os.path.join(DATA, 'shipment_status.json')
    ship = json.load(open(ship_file, encoding='utf-8'))
    orders = ship.get('orders', [])

    # Step 3: 刷新未签收订单的顺丰轨迹
    print(f"[3/4] 刷新 {len(orders)} 条订单轨迹...", file=sys.stderr)
    groups = {}
    for o in orders:
        tn = (o.get('trackingNo') or '').strip()
        if not tn or (o.get('shipment') or {}).get('status') == 'delivered': continue
        groups.setdefault(phone4(o), {})[tn] = o

    token = sf_get_token()
    results = {}; n_ok = n_total = 0
    for p4, tn_map in groups.items():
        tns = list(tn_map.keys())
        for i in range(0, len(tns), 10):
            batch = tns[i:i+10]; n_total += len(batch)
            try:
                raw = sf_query_routes(batch, token, p4)
                route_resps = raw.get('msgData', {}).get('routeResps', []) if raw.get('success') else []
                for resp in route_resps:
                    tn = resp.get('mailNo', '')
                    fine, last = fine_status(resp.get('routes', []), resp.get('reasonRemark', ''))
                    if fine not in ('no_data', 'unknown'):
                        results[tn] = (fine, last, resp.get('routes', [])); n_ok += 1
            except Exception: pass
            if i + 10 < len(tns): time.sleep(0.5)

    # 写回
    if n_total > 0 and n_ok / n_total < 0.5:
        print(f"⚠️ 顺丰API异常：{n_ok}/{n_total}，放弃写入", file=sys.stderr)
        sys.exit(1)
    for o in orders:
        tn = (o.get('trackingNo') or '').strip()
        if tn not in results: continue
        fine, last, routes = results[tn]
        coarse, label = COARSE[fine]
        if not coarse: continue
        sh = o.get('shipment') or {}
        sh['mailNo'] = tn; sh['status'] = coarse; sh['statusLabel'] = label
        if last: sh['lastRoute'] = last
        if routes: sh['routes'] = routes; sh['routeCount'] = len(routes)
        o['shipment'] = sh
    ship['updated_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
    json.dump(ship, open(ship_file, 'w', encoding='utf-8'), ensure_ascii=False)

    # 状态比对
    old_state = json.load(open(STATE, encoding='utf-8')) if os.path.exists(STATE) else None
    new_state = dict(old_state or {})
    for tn, (fine, _, _) in results.items(): new_state[tn] = fine
    json.dump(new_state, open(STATE, 'w', encoding='utf-8'), ensure_ascii=False)

    if old_state is None:
        n_del = sum(1 for o in orders if (o.get('shipment') or {}).get('status') == 'delivered')
        print(f"📦 顺丰监控已启动：{n_ok} 在途，{n_del} 已签收", file=sys.stderr)
        return

    # Step 4: 检测变化并推送到刘新元（临时统一推送，跑通后改回按销售员）
    print("[4/4] 检测物流变化并推送...", file=sys.stderr)
    changes = {'delivered': [], 'delivering': []}
    tn2order = {(o.get('trackingNo') or '').strip(): o for o in orders}
    for tn, (fine, _, _) in results.items():
        prev = old_state.get(tn, 'unknown')
        o = tn2order.get(tn)
        if not o: continue
        if fine == 'delivered' and prev != 'delivered': changes['delivered'].append(o)
        elif fine == 'delivering' and prev not in ('delivering', 'delivered'): changes['delivering'].append(o)

    if not changes['delivered'] and not changes['delivering']:
        return  # 无变化，静默

    target_email = 'liuxinyuan@biori.com'  # 临时：统一推送给自己，跑通后改回按销售员

    for kind, emoji, title in (('delivered', '✅', '新签收'), ('delivering', '🚚', '新派送')):
        items = changes[kind]
        if not items: continue
        lines = [f"{emoji} **{title} {len(items)} 单**", '']
        for o in items:
            amt = o.get('taxTotal') or o.get('amount') or 0
            try: amt_str = f"¥{float(amt):,.0f}"
            except: amt_str = str(amt)
            lines.append(f"• {o.get('customer','?')}｜{o.get('trackingNo','')}｜{amt_str}｜👤{o.get('creator','?')}")
        body = '\n'.join(lines)
        try:
            ok = send_feishu_card(target_email, f'📦 顺丰物流动态', body)
        except Exception as e:
            ok = False
            print(f"  ⚠️ 推送失败: {e}", file=sys.stderr)
        if ok:
            print(f"  ✅ {title} {len(items)}单 → {target_email}", file=sys.stderr)

    print(f"🎯 已推送给 {target_email}", file=sys.stderr)

if __name__ == '__main__':
    main()
