#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""从 2027 V1 单一数据源生成 WorkBuddy 的 PC/小程序基线。

输入：数据/客户商机演示_2027_v1.json
输出：
  workbuddy_v1/web/baseline.js
  workbuddy_v1/miniprogram/data/baseline.js
  workbuddy_v1/data/seed.json
  workbuddy_v1/data/build_info.json

目的：
1. 消除 web 与小程序两份 baseline.js 手工复制导致的漂移；
2. 不再静默截断活动：timeline 保留全部 3,628 条，与 filterStats.kept 对齐；
3. 让 WorkBuddy 原型的数据层可以从本地主数据一键重建。
"""
from __future__ import annotations

import hashlib
import json
import os
import re
from datetime import datetime

BASE = os.path.expanduser('~/Desktop/Hermes输出-工作类')
SRC = os.path.join(BASE, '数据', '客户商机演示_2027_v1.json')
OUT = os.path.join(BASE, '工作台工程', 'workbuddy_v1')


def norm_key(s: str) -> str:
    return re.sub(r'\s+', '', str(s or '').strip().lower())


def load_source():
    with open(SRC, encoding='utf-8') as f:
        return json.load(f)


def build_baseline(src: dict) -> dict:
    # 保持 WorkBuddy 原型使用的字段名；金额单位统一为万元。
    meta = dict(src.get('meta') or {})
    summary = dict(src.get('summary') or {})
    quality = dict(src.get('quality') or {})

    # 活动原始表在 V1 JSON 里是 activities（10,532 条）。
    # 按 WorkBuddy 口径只保留命中 230 家预测客户或 224 个集团归口的活动。
    customer_names = {(c.get('name') or '') for c in (src.get('customers') or [])}
    customer_originals = {(c.get('original') or '') for c in (src.get('customers') or [])}
    group_names = {(g.get('name') or '') for g in (src.get('groups') or [])}
    raw_activities = src.get('baseline_activities') or src.get('activities') or []
    activities = []
    for a in raw_activities:
        if (a.get('canonical') in customer_names or
                a.get('customer') in customer_names or
                a.get('customer') in customer_originals or
                a.get('group') in group_names):
            activities.append(a)
    filter_stats = dict(src.get('filter_stats') or src.get('filterStats') or {})
    if not filter_stats:
        filter_stats = {
            'total': len(raw_activities),
            'kept': len(activities),
            'dropped': len(raw_activities) - len(activities),
        }

    departments = []
    for d in src.get('departments') or []:
        x = dict(d)
        x.setdefault('target', None)
        x.setdefault('raw_gap', None)
        x.setdefault('weighted_gap', None)
        x.setdefault('growth', None)
        departments.append(x)

    people = []
    for p in src.get('people') or []:
        x = dict(p)
        x.setdefault('target', None)
        x.setdefault('raw_gap', None)
        x.setdefault('weighted_gap', None)
        x.setdefault('growth', None)
        people.append(x)

    regions = []
    for r in src.get('regions') or []:
        x = dict(r)
        x.setdefault('target', None)
        x.setdefault('raw_gap', None)
        x.setdefault('weighted_gap', None)
        x.setdefault('growth', None)
        regions.append(x)

    type_summary = []
    for t in src.get('type_summary') or src.get('typeSummary') or []:
        x = dict(t)
        x.setdefault('target', None)
        x.setdefault('raw_gap', None)
        x.setdefault('weighted_gap', None)
        x.setdefault('growth', None)
        type_summary.append(x)

    customers = []
    alias_index = {}
    for c in src.get('customers') or []:
        name = c.get('name') or c.get('canonical') or c.get('original')
        if not name:
            continue
        x = {
            'name': name,
            'original': c.get('original') or name,
            'group': c.get('group') or name,
            'aliases': list(c.get('aliases') or []),
            'status': c.get('mapping_status') or c.get('status') or '待确认',
            'raw': round(float(c.get('raw') or 0), 4),
            'weighted': round(float(c.get('weighted') or 0), 4),
            'count': int(c.get('count') or 0),
            'sales': c.get('sales') or '',
            'dept': c.get('dept') or '',
            'region': c.get('region') or '',
            'activities': int(c.get('activities') or 0),
            'lastActivity': c.get('last_activity') or c.get('lastActivity') or '',
            'opps': list(c.get('opps') or []),
        }
        customers.append(x)
        for a in [x['name'], x['original'], *x['aliases']]:
            k = norm_key(a)
            if k and k not in alias_index:
                alias_index[k] = name

    groups = []
    for g in src.get('groups') or []:
        name = g.get('name') or ''
        if not name:
            continue
        groups.append({
            'name': name,
            'raw': round(float(g.get('raw') or 0), 4),
            'weighted': round(float(g.get('weighted') or 0), 4),
            'count': int(g.get('count') or 0),
            'customerCount': int(g.get('customer_count') or g.get('customerCount') or 0),
            'types': dict(g.get('types') or {}),
            'sales': g.get('sales') or '',
            'region': g.get('region') or '',
            'activities': int(g.get('activities') or 0),
            'lastActivity': g.get('last_activity') or g.get('lastActivity') or '',
            'customers': list(g.get('customers') or []),
        })

    opportunities = []
    for o in src.get('opportunities') or []:
        opportunities.append({
            'id': o.get('id') or '',
            'customer': o.get('customer') or o.get('canonical') or '',
            'name': o.get('opp') or o.get('name') or '',
            'sku': o.get('sku') or '',
            'oid': o.get('oid') or '',
            'type': o.get('type') or '',
            'line': o.get('line') or '',
            'dept': o.get('dept') or '',
            'sales': o.get('sales') or '',
            'q': list(o.get('q') or []),
            'raw': float(o.get('raw') or 0),
            'prob': float(o.get('prob') or 0),
            'weighted': float(o.get('weighted') or 0),
            'group': o.get('group') or '',
            'region': o.get('region') or '',
            'status': o.get('mapping_status') or o.get('status') or '',
            'issue': o.get('issue') or '',
        })

    # 全部活动，不按客户 12 条截断；与 filterStats.kept 保持一致。
    timeline = []
    for a in activities:
        timeline.append({
            'date': a.get('date') or '',
            'customer': a.get('customer') or '',
            'type': a.get('type') or '',
            'sales': a.get('sales') or '',
            'dept': a.get('dept') or '',
            'region': a.get('region') or '',
            'group': a.get('group') or '',
            'text': a.get('text') or '',
            'source': a.get('source') or '',
        })
    timeline.sort(key=lambda x: (x.get('date') or '', x.get('customer') or ''), reverse=True)

    meta['timeline_count'] = len(timeline)
    meta['baseline_build_tool'] = 'build_workbuddy_baseline.py'
    meta['baseline_source_json'] = '数据/客户商机演示_2027_v1.json'

    baseline = {
        'meta': meta,
        'summary': summary,
        'departments': departments,
        'people': people,
        'regions': regions,
        'typeSummary': type_summary,
        'quality': quality,
        'filterStats': filter_stats,
        'customers': customers,
        'groups': groups,
        'opportunities': opportunities,
        'aliasIndex': alias_index,
        'timeline': timeline,
    }
    return baseline


def truncate_timeline(b: dict, per_customer: int = 12) -> dict:
    """小程序主包有 2MB 限制：按客户保留最近 N 条，并显式记录总量。"""
    import copy
    out = copy.deepcopy(b)
    by_customer = {}
    for row in out['timeline']:
        by_customer.setdefault(row.get('customer') or '', []).append(row)
    kept = []
    for rows in by_customer.values():
        kept.extend(rows[:per_customer])
    kept.sort(key=lambda x: (x.get('date') or '', x.get('customer') or ''), reverse=True)
    out['timeline'] = kept
    out['meta']['timeline_total'] = len(b['timeline'])
    out['meta']['timeline_displayed'] = len(kept)
    out['meta']['timeline_policy'] = '小程序按客户保留最近 %d 条；PC 大盘为全量' % per_customer
    out['filterStats'] = dict(out.get('filterStats') or {})
    out['filterStats']['kept'] = len(b['timeline'])
    out['filterStats']['timeline_displayed'] = len(kept)
    return out


def validate(b: dict):
    errs = []
    if len(b['opportunities']) != 341:
        errs.append('opportunities != 341: %d' % len(b['opportunities']))
    if len(b['customers']) != 230:
        errs.append('customers != 230: %d' % len(b['customers']))
    if len(b['groups']) != 224:
        errs.append('groups != 224: %d' % len(b['groups']))
    if len(b['timeline']) != 3628:
        errs.append('timeline != 3628: %d' % len(b['timeline']))
    if len(b['aliasIndex']) < 230:
        errs.append('aliasIndex < 230: %d' % len(b['aliasIndex']))
    raw = sum(float(x.get('raw') or 0) for x in b['opportunities']) / 10000.0
    weighted = sum(float(x.get('weighted') or 0) for x in b['opportunities']) / 10000.0
    if abs(raw - b['summary']['raw_total_wan']) > 0.01:
        errs.append('raw sum %.4f != summary %.4f' % (raw, b['summary']['raw_total_wan']))
    if abs(weighted - b['summary']['weighted_total_wan']) > 0.01:
        errs.append('weighted sum %.4f != summary %.4f' % (weighted, b['summary']['weighted_total_wan']))
    return errs


def write_outputs(b: dict, mini: dict):
    os.makedirs(os.path.join(OUT, 'web'), exist_ok=True)
    os.makedirs(os.path.join(OUT, 'miniprogram', 'data'), exist_ok=True)
    os.makedirs(os.path.join(OUT, 'data'), exist_ok=True)
    text = json.dumps(b, ensure_ascii=False, separators=(',', ':'))
    mini_text = json.dumps(mini, ensure_ascii=False, separators=(',', ':'))
    web_js = '// 2027 V1 冻结快照 · 由 build_workbuddy_baseline.py 生成，禁止手工修改\nwindow.__BASELINE__ = ' + text + ';\n'
    mini_js = '// 2027 V1 冻结快照 · 由 build_workbuddy_baseline.py 生成，禁止手工修改\nmodule.exports = ' + mini_text + ';\n'
    for rel, content in [
        ('web/baseline.js', web_js),
        ('miniprogram/data/baseline.js', mini_js),
        ('data/seed.json', json.dumps(b, ensure_ascii=False, indent=1)),
    ]:
        path = os.path.join(OUT, rel)
        with open(path, 'w', encoding='utf-8') as f:
            f.write(content)
    info = {
        'generated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        'source': SRC,
        'source_sha256': hashlib.sha256(open(SRC, 'rb').read()).hexdigest(),
        'counts': {
            'opportunities': len(b['opportunities']),
            'customers': len(b['customers']),
            'groups': len(b['groups']),
            'timeline': len(b['timeline']),
            'timeline_mini': len(mini['timeline']),
            'alias_index': len(b['aliasIndex']),
        },
        'summary': b['summary'],
        'outputs': ['web/baseline.js', 'miniprogram/data/baseline.js', 'data/seed.json'],
    }
    with open(os.path.join(OUT, 'data', 'build_info.json'), 'w', encoding='utf-8') as f:
        json.dump(info, f, ensure_ascii=False, indent=2)
    return info


if __name__ == '__main__':
    src = load_source()
    baseline = build_baseline(src)
    errors = validate(baseline)
    if errors:
        raise SystemExit('validation failed: ' + '; '.join(errors))
    mini = truncate_timeline(baseline, per_customer=12)
    info = write_outputs(baseline, mini)
    print(json.dumps(info, ensure_ascii=False, indent=2))
