#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
客户360°档案卡数据聚合器
输入（数据/目录）：
  pricing/_index.json + pricing/<客户>.json  → 基础信息/集团/负责人/主销产品
  cust_yearly.json                           → 2024/2025/2026 年度采购额
  mcp_activities.json                        → 活动记录（拜访/线上）
  opps_by_customer.json + opportunity_sandbox.json → 商机
  mcp_complaints.json                        → 客诉
  shipment_status.json                       → H2订单+物流
输出：数据/customer_360.json（单文件，前端一次加载，客户端搜索）
"""
import json, os, time, datetime, collections

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA = os.path.join(ROOT, '数据')
PRICING = os.path.join(DATA, 'pricing')


def load(fn, default=None):
    p = os.path.join(DATA, fn)
    if not os.path.exists(p):
        return default
    return json.load(open(p, encoding='utf-8'))


def norm(n):
    return (n or '').strip()


import re
REGION_SFX = re.compile(r'-(浙江|广东|江苏|北京|上海|华北|华南|华中|华东|华西|山东|四川|福建|河南|河北|湖南|湖北|安徽|重庆|天津|陕西)$')


def strip_sfx(n):
    return REGION_SFX.sub('', norm(n))


def dedupe(seq, key_fn):
    seen = set()
    out = []
    for item in seq:
        k = key_fn(item)
        if k not in seen:
            seen.add(k)
            out.append(item)
    return out


def sum_field(items, field):
    return round(sum(float(x.get(field) or 0) for x in items))


def build_customer(entry, yearly, act_by_cust, opp_sandbox_by_cust, opps_bc,
                   comp_by_cust, orders_by_cust, h2_spend, levels):
    name = norm(entry.get('name'))
    if not name or name.lower() == 'test':
        return None
    fn = entry.get('file') or name
    lv = levels.get(name) or levels.get(strip_sfx(name)) or {}
    mcp_level, mcp_tier = lv.get('level', ''), lv.get('tier', '')
    detail = {}
    pj = os.path.join(PRICING, fn + '.json')
    if os.path.exists(pj):
        try:
            detail = json.load(open(pj, encoding='utf-8'))
        except Exception:
            detail = {}

    # 主销产品 top5（排除赠品）
    prods = [p for p in (detail.get('products') or []) if not p.get('is_gift')]
    prods.sort(key=lambda p: -(p.get('total_amt') or 0))
    top_products = [{'sku': p.get('sku'), 'name': (p.get('name') or '')[:30],
                     'amt': round(p.get('total_amt') or 0)} for p in prods[:5]]

    y = yearly.get(name) or {}
    y24, y25, y26 = y.get('2024', 0) or 0, y.get('2025', 0) or 0, y.get('2026', 0) or 0

    acts_c = sorted(act_by_cust.get(name, []), key=lambda a: a['date'], reverse=True)[:8]
    opps_live = (opps_bc.get(name) or [])[:10]
    opps_live = [{'name': norm(o.get('name')), 'status': norm(o.get('status')),
                  'creator': norm(o.get('creator')), 'url': o.get('url') or ''} for o in opps_live]
    comps = sorted(comp_by_cust.get(name, []), key=lambda c: c['date'], reverse=True)[:10]

    ords = sorted(orders_by_cust.get(name, []), key=lambda o: o.get('createDate') or '', reverse=True)[:8]
    ords_out = [{
        'id': o.get('orderId'), 'date': o.get('createDate'),
        'product': (o.get('product') or '')[:30],
        'amt': round(float(o.get('taxTotal') or o.get('amount') or 0) or 0),
        'ship': ((o.get('shipment') or {}).get('statusLabel')) or ('无物流' if not o.get('trackingNo') else '未知'),
    } for o in ords]

    risks = []
    if y26 == 0 and (y24 > 0 or y25 > 0):
        risks.append('今年无采购')
    elif y25 > 0 and 0 < y26 < y25 * 0.25:
        risks.append('采购大幅下滑')
    open_comps = [c for c in comps if not any(k in c['status'] for k in ('完成', '关闭', '解决'))]
    if open_comps:
        risks.append(f"客诉进行中×{len(open_comps)}")

    return {
        'name': name,
        'short': norm(detail.get('short_name')) or name[:12],
        'group': norm(detail.get('group')) or norm(entry.get('group_name')),
        'sales': norm(detail.get('salesperson')),
        'level': mcp_level,
        'tier': mcp_tier,
        'total': round(entry.get('total') or 0),
        'orders': entry.get('orders') or 0,
        'contacts': entry.get('contacts') or 0,
        'latest_date': entry.get('latest_date') or '',
        'y2024': round(y24), 'y2025': round(y25), 'y2026': round(y26),
        'h2_2026': round(h2_spend.get(name, 0)),
        'top_products': top_products,
        'activities': acts_c,
        'opps': opps_live,
        'opp_sandbox': opp_sandbox_by_cust.get(name, [])[:8],
        'complaints': comps,
        'recent_orders': ords_out,
        'risks': risks,
    }


def build_group(members):
    # 集团级聚合
    total = round(sum(c['total'] for c in members))
    y24 = round(sum(c['y2024'] for c in members))
    y25 = round(sum(c['y2025'] for c in members))
    y26 = round(sum(c['y2026'] for c in members))
    h2 = round(sum(c['h2_2026'] for c in members))
    orders = sum(c['orders'] for c in members)
    contacts = sum(c['contacts'] for c in members)

    # 主销产品聚合（按 sku+名称）
    prod_map = {}
    for c in members:
        for p in c.get('top_products') or []:
            key = (norm(p.get('sku')) or norm(p.get('name')), p.get('name'))
            prod_map.setdefault(key, {'sku': p.get('sku'), 'name': p.get('name'), 'amt': 0})
            prod_map[key]['amt'] += p.get('amt') or 0
    top_products = sorted(prod_map.values(), key=lambda p: -p['amt'])[:5]

    # 商机/客诉/订单/活动去重合并
    opps = dedupe([o for c in members for o in c.get('opps') or []], lambda o: o.get('url') or o.get('name'))
    opp_sandbox = dedupe([o for c in members for o in c.get('opp_sandbox') or []],
                         lambda o: (o.get('name'), o.get('sales'), o.get('type')))
    complaints = dedupe([cp for c in members for cp in c.get('complaints') or []], lambda cp: cp.get('id'))
    recent_orders = dedupe([o for c in members for o in c.get('recent_orders') or []], lambda o: o.get('id'))
    activities = dedupe([a for c in members for a in c.get('activities') or []],
                        lambda a: (a.get('name'), a.get('date'), a.get('mode')))
    activities.sort(key=lambda a: a['date'], reverse=True)

    latest_date = max((c['latest_date'] for c in members if c.get('latest_date')), default='')
    sales_list = sorted({c['sales'] for c in members if c.get('sales')})
    members_sorted = sorted(members, key=lambda c: -c['total'])

    # 集团级别风险
    risks = []
    if y26 == 0 and (y24 > 0 or y25 > 0):
        risks.append('今年无采购')
    elif y25 > 0 and 0 < y26 < y25 * 0.25:
        risks.append('采购大幅下滑')
    open_comps = [c for c in complaints if not any(k in c['status'] for k in ('完成', '关闭', '解决'))]
    if open_comps:
        risks.append(f"客诉进行中×{len(open_comps)}")

    # 集团等级：只要有一家大客户即大客户
    level = ''
    if any(c.get('level') == '大客户' for c in members):
        level = '大客户'
    elif any(c.get('level') == '重点客户' for c in members):
        level = '重点客户'

    return {
        'name': norm(members[0].get('group')),
        'members': [c['name'] for c in members_sorted],
        'member_totals': [{'name': c['name'], 'total': c['total']} for c in members_sorted],
        'count': len(members),
        'sales': '、'.join(sales_list) if sales_list else '',
        'level': level,
        'total': total,
        'orders': orders,
        'contacts': contacts,
        'latest_date': latest_date,
        'y2024': y24, 'y2025': y25, 'y2026': y26,
        'h2_2026': h2,
        'top_products': top_products,
        'activities': activities[:8],
        'opps': opps[:10],
        'opp_sandbox': opp_sandbox[:8],
        'complaints': complaints[:10],
        'recent_orders': sorted(recent_orders, key=lambda o: o.get('date') or '', reverse=True)[:8],
        'risks': risks,
    }


def main():
    t0 = time.time()
    idx = load('pricing/_index.json', [])
    yearly = load('cust_yearly.json', {})
    acts = load('mcp_activities.json', {}).get('items', [])
    opps_bc = load('opps_by_customer.json', {})
    sandbox = load('opportunity_sandbox.json', {}).get('opportunities', [])
    complaints = load('mcp_complaints.json', {}).get('items', [])
    ship = load('shipment_status.json', {}).get('orders', [])
    levels = load('customer_levels.json', {})

    # ── 按客户预分组 ─────────────────────────────
    act_by_cust = collections.defaultdict(list)
    for a in acts:
        c = norm(a.get('field_5f20fc'))
        if not c:
            continue
        act_by_cust[c].append({
            'date': (a.get('start_time') or '')[:10],
            'name': norm(a.get('name')),
            'mode': norm(a.get('field_76654e'))[:12],
            'snippet': norm(a.get('field_b99055')).replace('\n', ' ')[:100],
            'owner': ((a.get('owner') or {}).get('name_cn') if isinstance(a.get('owner'), dict) else '') or '',
        })
    # 去重：同一客户同一名称+日期只留一条
    for c in act_by_cust:
        act_by_cust[c] = dedupe(act_by_cust[c], lambda a: (a['name'], a['date']))

    opp_sandbox_by_cust = collections.defaultdict(list)
    for o in sandbox:
        opp_sandbox_by_cust[norm(o.get('customer'))].append({
            'name': norm(o.get('name')), 'target': o.get('target') or 0,
            'sales': norm(o.get('sales')), 'type': norm(o.get('type')),
        })

    comp_by_cust = collections.defaultdict(list)
    for c in complaints:
        labels = c.get('field_ac5caf') or []
        if isinstance(labels, dict):
            labels = [labels]
        st = ''
        ws = c.get('work_item_status') or []
        if ws and isinstance(ws[0], dict):
            st = ws[0].get('label', '')
        for item in labels:
            if isinstance(item, dict) and item.get('label'):
                comp_by_cust[norm(item['label'])].append({
                    'name': norm(c.get('name')), 'status': st,
                    'date': (c.get('start_time') or '')[:10],
                    'id': c.get('work_item_id'),
                })
    for c in comp_by_cust:
        comp_by_cust[c] = dedupe(comp_by_cust[c], lambda cp: cp['id'])

    orders_by_cust = collections.defaultdict(list)
    h2_spend = collections.defaultdict(float)
    for o in ship:
        for key in {norm(o.get('mcpCustomer')), norm(o.get('customer'))} - {''}:
            orders_by_cust[key].append(o)
        cust = norm(o.get('mcpCustomer')) or norm(o.get('customer'))
        try:
            h2_spend[cust] += float(o.get('taxTotal') or o.get('amount') or 0)
        except Exception:
            pass
    for c in orders_by_cust:
        orders_by_cust[c] = dedupe(orders_by_cust[c], lambda o: o.get('orderId'))

    # ── 逐客户聚合 ─────────────────────────────
    customers = []
    for entry in idx:
        c = build_customer(entry, yearly, act_by_cust, opp_sandbox_by_cust,
                           opps_bc, comp_by_cust, orders_by_cust, h2_spend, levels)
        if c:
            customers.append(c)

    customers.sort(key=lambda c: -c['total'])

    # ── 集团聚合 ─────────────────────────────
    group_map = collections.defaultdict(list)
    for c in customers:
        g = c.get('group') or ''
        if g:
            group_map[g].append(c)
    groups = [build_group(members) for members in group_map.values() if len(members) >= 2]
    groups.sort(key=lambda g: -g['total'])

    # ── 补充：MCP打了级别但无ERP记录的新客户（占位卡）──
    existing = {c['name'] for c in customers} | {strip_sfx(c['name']) for c in customers}
    stubs = 0
    for lname, lv in levels.items():
        if lname in existing or strip_sfx(lname) in existing:
            continue
        nm = norm(lname)
        customers.append({
            'name': nm, 'short': nm[:12], 'group': '', 'sales': '',
            'level': lv.get('level', ''), 'tier': lv.get('tier', ''),
            'total': 0, 'orders': 0, 'contacts': 0, 'latest_date': '',
            'y2024': 0, 'y2025': 0, 'y2026': 0, 'h2_2026': 0,
            'top_products': [], 'activities': [], 'opps': [], 'opp_sandbox': [],
            'complaints': [], 'recent_orders': [], 'risks': ['ERP无采购记录(新客户)'],
        })
        stubs += 1
    if stubs:
        print(f"  +{stubs} 个MCP标签客户无ERP记录，已补占位卡")

    out = {
        'updated_at': time.strftime('%Y-%m-%d %H:%M:%S'),
        'count': len(customers),
        'group_count': len(groups),
        'customers': customers,
        'groups': groups,
    }
    outp = os.path.join(DATA, 'customer_360.json')
    json.dump(out, open(outp, 'w', encoding='utf-8'), ensure_ascii=False)
    size = os.path.getsize(outp) // 1024
    print(f"✅ {len(customers)} 客户 / {len(groups)} 集团 → customer_360.json ({size}KB), 耗时 {time.time()-t0:.1f}s")
    stats = {
        '有活动': sum(1 for c in customers if c['activities']),
        '有商机': sum(1 for c in customers if c['opps'] or c['opp_sandbox']),
        '有客诉': sum(1 for c in customers if c['complaints']),
        '有H2订单': sum(1 for c in customers if c['recent_orders']),
        '有风险标记': sum(1 for c in customers if c['risks']),
    }
    print("覆盖率:", stats)


if __name__ == '__main__':
    main()
