#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""修复 shipment_status.json 里「查询失败」的订单 —— 逐单重查顺丰，能救回的救回。

用法：
    python3 repair_shipment_no_data.py            # 只报告，不写盘
    python3 repair_shipment_no_data.py --apply    # 写回本文件

背景（2026-09-30 用户报障「订单已到货但工作台物流信息缺失」）：
    管道每轮全量重查 1300+ 单号，一次限流/手机号不匹配就把已签收的单覆写成 no_data。
    本脚本对 no_data / error / phone_mismatch 三类单逐单重查（带重试），
    查回来的写回；仍查不到的按原因分类，输出可执行清单。
"""
import json, io, os, sys, time, importlib.util

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
F = os.path.join(ROOT, '数据', 'shipment_status.json')
APPLY = '--apply' in sys.argv

spec = importlib.util.spec_from_file_location('sp', os.path.join(ROOT, '脚本', 'shipment_pipeline.py'))
sp = importlib.util.module_from_spec(spec); spec.loader.exec_module(sp)

D = json.load(io.open(F, encoding='utf-8'))
O = D['orders']

BAD = ('no_data', 'error', 'phone_mismatch', 'unknown')
targets = [o for o in O if (o.get('trackingNo') or '').strip() and ((o.get('shipment') or {}).get('status') in BAD)]
print(f"待修复（有单号但查询失败）: {len(targets)} 单\n")
if not targets:
    sys.exit(0)

tok = sp.sf_get_token()
fixed = still = 0
reasons = {}
for i, o in enumerate(targets, 1):
    tn = o['trackingNo'].strip()
    digits = "".join(c for c in str(o.get('phone') or '') if c.isdigit())
    p4 = digits[-4:] if len(digits) >= 4 else None
    got, reason = None, ''
    for att in range(3):
        try:
            r = sp.sf_query_routes([tn], tok, p4)
            if isinstance(r, dict) and 'error' not in r:
                pr = sp.parse_routes(r)
                info = pr.get(tn) if isinstance(pr, dict) else None
                if info and info.get('routes'):
                    got = info; break
                reason = (info or {}).get('reason') or '暂无路由信息'
                if '手机号校验失败' in str(reason) or '月结卡号' in str(reason):
                    break
            else:
                reason = str(r)
        except Exception as e:
            reason = str(e)
        if att < 2: time.sleep(1.2 * (att + 1))

    if got:
        old_routes = ((o.get('shipment') or {}).get('routes') or [])
        got.pop('stale', None); got.pop('staleReason', None); got.pop('staleAt', None)
        o['shipment'] = got
        fixed += 1
        print('  ✅ %-16s %-20s %-16s → %s（%d 条轨迹）' % (
            o['orderId'], (o.get('customer') or '')[:18], tn, got.get('statusLabel'), len(got.get('routes'))))
    else:
        still += 1
        reasons.setdefault(reason, []).append(o)
        print('  ⚠️ %-16s %-20s %-16s 仍失败: %s' % (
            o['orderId'], (o.get('customer') or '')[:18], tn, reason))
    time.sleep(0.15)

print(f"\n救回 {fixed} 单，仍失败 {still} 单")
if reasons:
    print('\n仍失败按原因分组:')
    for r, lst in sorted(reasons.items(), key=lambda x: -len(x[1])):
        print('  【%s】%d 单' % (r or '(空)', len(lst)))

if APPLY and fixed:
    D['updated_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
    with io.open(F, 'w', encoding='utf-8') as f:
        json.dump(D, f, ensure_ascii=False, indent=2)
    print(f"\n✅ 已写回 {F}")
elif fixed:
    print("\n（未加 --apply，未写盘）")
