#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
价格查询接口（服务端）
======================
数据底座：数据/price_index.json（由 脚本/build_price_index.py 生成）
路由：GET /api/price/query?sku=<货号或关键词>&customer=<客户名，可选>

权限口径（服务端强制，前端只是展示层）：
  · scope = all  ：role 为 admin/gm，或权限面板把该人「价格查询」勾成「全量」
                  → 可见全部客户明细（含客户名与价）
  · scope = own  ：默认（含所有 SALES）→ 明细只返回 销售员 == 本人姓名 的记录，
                  其他客户的客户名/价一律不出现在响应里
  · scope = none ：权限面板勾「不可见」→ 403
  · 全库区间（最低/中位/最高/客户数）对所有被授权者可见，但绝不点名其他客户
  特殊口径：role=='gm' 默认全量，可被面板下调（面板值优先）。

权限存储：数据/user_permissions.json（与 ai_chat._load_user_permissions 同一份文件）
         字段 price_scope ∈ {'all','own','none'}
"""

import json
import os
import sys
import threading
import time
from urllib.parse import parse_qs

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(SCRIPT_DIR)
DATA_DIR = os.path.join(ROOT, '数据')
INDEX_FILE = os.path.join(DATA_DIR, 'price_index.json')
PERM_FILE = os.path.join(DATA_DIR, 'user_permissions.json')

_LOCK = threading.Lock()
_CACHE = {'mtime': 0.0, 'data': None}

SCOPE_LABEL = {'all': '全量明细', 'own': '仅自己客户', 'none': '不可见'}
DISCLAIMER = ('价格口径：ERP 含税单价 > 0，已排除赠品与预测单（FO 开头）；'
              '2024/2026 日期为日粒度、2025 为月粒度；'
              '同一货号可能同时以 U / ml / 瓶 成交，比价须在同一单位内进行。')


# ────────────────────────── 载入 & 权限 ──────────────────────────
def load_index():
    """带 mtime 缓存的索引加载（换新 ERP 重跑脚本后自动生效，无需重启）"""
    if not os.path.exists(INDEX_FILE):
        raise RuntimeError('price_index.json 不存在，请先运行 脚本/build_price_index.py')
    mt = os.path.getmtime(INDEX_FILE)
    with _LOCK:
        if _CACHE['data'] is None or _CACHE['mtime'] != mt:
            with open(INDEX_FILE, encoding='utf-8') as f:
                _CACHE['data'] = json.load(f)
            _CACHE['mtime'] = mt
        return _CACHE['data']


def load_perms():
    try:
        with open(PERM_FILE, encoding='utf-8') as f:
            d = json.load(f)
        return d if isinstance(d, dict) else {}
    except Exception:
        return {}


def scope_for(user):
    """解析该用户的「价格查询」口径：all / own / none"""
    user = user or {}
    role = (user.get('role') or 'sales')
    name = (user.get('name') or '')
    default = 'all' if role in ('admin', 'gm', 'readonly') else 'own'
    p = (load_perms().get(name) or {})
    s = str(p.get('price_scope') or '').strip().lower()
    if s not in ('all', 'own', 'none'):
        s = default
    return s, default


def aliases_for(user):
    """本人可视为「自己」的其他 ERP 销售员名（默认空；仅显式配置才生效）。
    用途：ERP 里 大客户1/大客户2 这类占位名下挂的成交，若管理员确认某人应看，配 price_alias。"""
    name = _nz((user or {}).get('name'))
    p = (load_perms().get(name) or {})
    out = [name]
    for a in (p.get('price_alias') or []):
        a = _nz(str(a))
        if a and a not in out:
            out.append(a)
    return out


def perm_matrix():
    """给权限面板用：每个人的价格查询口径（默认值来自角色）"""
    out = {}
    for nm, p in load_perms().items():
        v = str((p or {}).get('price_scope') or '').strip().lower()
        if v in ('all', 'own', 'none'):
            out[nm] = v
    return out


# ────────────────────────── 查询 ──────────────────────────
def _nz(s):
    return (s or '').strip()


def match_skus(idx, kw, limit=8):
    """货号/产品名关键词 → 候选 SKU（打分排序：货号精确 > 货号前缀 > 名称精确 > 名称包含）"""
    k = kw.lower().strip()
    if not k:
        return []
    kcompact = k.replace(' ', '')
    scored = []
    for sku, stext in (idx.get('search') or {}).items():
        s_low = sku.lower()
        sc = 0
        if s_low == k or s_low == kcompact:
            sc = 1000
        elif s_low.startswith(k) or s_low.startswith(kcompact):
            sc = 700
        elif k in s_low:
            sc = 500
        if sc < 500 and k in stext:
            # 名称/规格/品类命中：越靠前越像名称
            base = 400 if k in stext.split(' ')[0] else 200
            sc = base
        if sc:
            rec = idx['skus'][sku]
            n = len(rec.get('records') or [])
            scored.append((sc + min(n, 50), sku))
    scored.sort(key=lambda x: (-x[0], x[1]))
    return [s for _, s in scored[:limit]]


def _strip_private(stats):
    """对方可见的全库聚合：去掉可能反查到具体单据的字段（单据号）"""
    if not stats:
        return None
    s = dict(stats)
    if isinstance(s.get('last'), dict):
        s['last'] = {k: v for k, v in s['last'].items() if k != 'order'}
    return s


def _agg(records):
    """与 build_price_index.agg 同口径的轻量聚合（用于「仅自己」子集）"""
    ps = sorted(r['price'] for r in records if r.get('price') is not None)
    if not ps:
        return None

    def q(vals, qq):
        if len(vals) == 1:
            return round(vals[0], 6)
        pos = (len(vals) - 1) * qq
        lo = int(pos)
        hi = min(lo + 1, len(vals) - 1)
        return round(vals[lo] * (1 - (pos - lo)) + vals[hi] * (pos - lo), 6)

    qsum = sum((r.get('qty') or 0) for r in records)
    tsum = sum((r.get('total') or 0) for r in records)
    units = sorted(set((r.get('unit') or '') for r in records if r.get('unit')))
    latest = max(records, key=lambda r: (str(r.get('date') or ''), str(r.get('order') or '')))
    return {
        'min': round(ps[0], 6), 'p25': q(ps, 0.25), 'p50': q(ps, 0.50), 'p75': q(ps, 0.75),
        'max': round(ps[-1], 6), 'avg': round(sum(ps) / len(ps), 6),
        'weighted_avg': round(tsum / qsum, 6) if qsum else None,
        'deal_count': len(records), 'customer_count': len(set(r['customer'] for r in records if r.get('customer'))),
        'qty_total': round(qsum, 3), 'amount_total': round(tsum, 2), 'units': units,
        'last': {'price': round(latest['price'], 6), 'date': latest.get('date'),
                 'qty': latest.get('qty'), 'unit': latest.get('unit') or ''},
    }


def _delta(a, b):
    """a 相对 b 的高/低百分比（b>0）"""
    try:
        if not b:
            return None
        return round((a - b) / b * 100.0, 1)
    except Exception:
        return None


def query(user, sku_kw='', customer='', record_limit=300, year=''):
    idx = load_index()
    scope, default_scope = scope_for(user)
    name = _nz((user or {}).get('name'))
    role = (user or {}).get('role') or 'sales'
    if scope == 'none':
        return {'ok': False, 'error': '无权查看价格数据（权限面板：价格查询 = 不可见）',
                'code': 'price_forbidden', 'scope': scope}, 403

    sku_kw = _nz(sku_kw)
    customer = _nz(customer)
    year = _nz(str(year or ''))
    if year not in ('2024', '2025', '2026'):
        year = ''                     # '' = 全部年份（保持原口径）
    years_meta = idx['meta'].get('by_year') or {}

    def yf(rs):
        """按所选年份过滤明细；year 为空＝不过滤（与 old 行为完全一致）"""
        if not year:
            return list(rs)
        return [r for r in rs if str(r.get('year')) == year]

    base = {'ok': True, 'scope': scope, 'scope_default': default_scope,
            'scope_label': SCOPE_LABEL.get(scope, scope), 'viewer': name, 'role': role,
            'keyword': sku_kw, 'customer_filter': customer, 'disclaimer': DISCLAIMER,
            'year': year or 'all',
            'year_options': [{'year': 'all', 'label': '全部',
                              'record_count': idx['meta'].get('record_count'),
                              'sku_count': idx['meta'].get('sku_with_price'),
                              'customer_count': idx['meta'].get('customer_count')}] +
                            [{'year': y, 'label': y,
                              'record_count': (years_meta.get(y) or {}).get('record_count', 0),
                              'sku_count': (years_meta.get(y) or {}).get('sku_count', 0),
                              'customer_count': (years_meta.get(y) or {}).get('customer_count', 0)}
                             for y in ('2024', '2025', '2026')],
            'meta': {k: idx['meta'].get(k) for k in
                     ('generated', 'year_range', 'criteria', 'sku_with_price',
                      'record_count', 'customer_count', 'sources', 'by_year')}}

    if not sku_kw:
        if customer:
            # ── 客户维度：只给「该客户 × 可见明细（× 所选年份）」的货号清单 ──
            al0 = aliases_for(user)
            rows0 = []
            for sku, rec in idx['skus'].items():
                rs = yf(rec.get('records') or [])
                if scope == 'all':
                    vis0 = [r for r in rs if customer in (r.get('customer') or '')]
                else:
                    vis0 = [r for r in rs if customer in (r.get('customer') or '')
                            and _nz(r.get('sales')) in al0]
                if not vis0:
                    continue
                a = _agg(vis0)
                a['sku'] = sku
                a['name'] = (rec.get('product') or {}).get('name') or ''
                a['spec'] = (rec.get('product') or {}).get('spec') or ''
                a['cat'] = (rec.get('product') or {}).get('cat') or ''
                a['life_months'] = (rec.get('product') or {}).get('life_months')
                rows0.append(a)
            rows0.sort(key=lambda z: -(z.get('amount_total') or 0))
            base['customer'] = customer
            base['customer_skus'] = rows0[:60]
            base['customer_sku_total'] = len(rows0)
            base['hint'] = ('客户「%s」名下 %d 个货号有成交价（按金额排序，最多显示 60 个）；'
                            '点货号或直接在上面输入货号可看该货号的价格区间。' % (customer, len(rows0)))
            if scope == 'own':
                base['own_note'] = ('当前口径：仅显示销售员为「%s」的成交明细；'
                                    '全库区间对所有授权者可见，但不含其他客户名。' % name)
            return base, 200
        top = []
        for sku, rec in list(idx['skus'].items()):
            if year:
                n = len(yf(rec.get('records') or []))
                if n:
                    top.append((n, sku))
            elif rec.get('stats'):
                top.append((rec['stats']['deal_count'], sku))
        top.sort(reverse=True)
        base['matches'] = []
        base['hot'] = [{'sku': s, 'name': (idx['skus'][s]['product'].get('name') or ''),
                        'deal_count': n} for n, s in top[:12]]
        base['hint'] = ('输入货号（如 2AM101-1）或产品名关键词（如 FastAmpli）；'
                        '年份切换只影响明细与区间，不改货号总量') if year else \
                       '输入货号（如 2AM101-1）或产品名关键词（如 FastAmpli）'
        return base, 200

    cands = match_skus(idx, sku_kw)
    if not cands:
        base['matches'] = []
        base['found'] = False
        base['hint'] = '没有匹配的货号或产品名；当前价格索引覆盖 %s 年 ERP 成交记录（%d 个货号有成交价）' % (
            idx['meta'].get('year_range'), idx['meta'].get('sku_with_price'))
        return base, 200

    primary = cands[0]
    rec = idx['skus'][primary]
    prod = dict(rec.get('product') or {})
    prod['sku'] = primary
    prod['in_master'] = prod.get('source') != 'erp_only'
    prod['unlisted'] = not prod['in_master']
    if prod.get('source') == 'component_of':
        prod['unlisted'] = True
    base['found'] = True
    base['sku'] = primary
    base['product'] = prod
    base['matches'] = [{'sku': s, 'name': (idx['skus'][s]['product'].get('name') or ''),
                        'spec': (idx['skus'][s]['product'].get('spec') or ''),
                        'cat': (idx['skus'][s]['product'].get('cat') or ''),
                        'life_months': idx['skus'][s]['product'].get('life_months'),
                        'source': idx['skus'][s]['product'].get('source'),
                        'deal_count': (idx['skus'][s].get('stats') or {}).get('deal_count', 0)}
                       for s in cands]

    all_recs = yf(rec.get('records') or [])
    stats_all = rec.get('stats') or {}
    if year:
        market = _strip_private(_agg(all_recs)) if all_recs else None
    else:
        market = _strip_private(stats_all)
    if market is not None:
        market['by_year'] = stats_all.get('by_year')      # 年度分布（全库聚合，不点名客户）
    base['market'] = market
    base['market_year'] = year or 'all'
    base['sku_by_year'] = stats_all.get('by_year') or {}
    base['units'] = stats_all.get('units') or []
    base['by_unit'] = stats_all.get('by_unit') or {}

    # ── 可见明细：own 只看 销售员==本人姓名（可含管理员显式配置的别名）──
    al = aliases_for(user)
    if scope == 'all':
        vis = list(all_recs)
    else:
        vis = [r for r in all_recs if _nz(r.get('sales')) in al]
    if customer:
        vis = [r for r in vis if customer in (r.get('customer') or '')]
    mine = _agg(vis) if vis else None
    base['mine'] = mine
    if scope == 'own':
        base['own_note'] = ('当前口径：仅显示销售员为「%s」的成交明细（%d 条）；'
                            '全库区间对所有授权者可见，但不含其他客户名。' % (name, len(vis)))

    # ── 按客户（可见范围内）──
    bcs = {}
    for r in vis:
        bcs.setdefault(r.get('customer') or '—', []).append(r)
    rows = []
    for c, rs in bcs.items():
        a = _agg(rs)
        a['customer'] = c
        a['is_mine'] = True
        a['last_sales'] = sorted(set(x.get('sales') for x in rs if x.get('sales')))
        a['vs_market_p50'] = _delta(a['last']['price'], (market or {}).get('p50')) if market else None
        rows.append(a)
    rows.sort(key=lambda z: -(z.get('amount_total') or 0))
    base['by_customer'] = rows

    # 客户维度对比（自己 vs 全库）
    if market and rows:
        base['compare'] = {
            'customer_scope': 'selected' if customer else 'self_all',
            'vs_market_p50_pct': rows[0]['vs_market_p50'] if customer else
            _delta(min(r['last']['price'] for r in rows), market['p50']),
            'market': {k: market.get(k) for k in ('min', 'p50', 'max', 'weighted_avg')},
        }

    # ── 明细（最近优先，截断保护）──
    vis_sorted = sorted(vis, key=lambda r: (str(r.get('date') or ''), str(r.get('order') or '')), reverse=True)
    base['records_total'] = len(vis_sorted)
    base['truncated'] = max(0, len(vis_sorted) - record_limit)
    base['records'] = []
    for r in vis_sorted[:record_limit]:
        out = {'date': r.get('date'), 'customer': r.get('customer'), 'unit': r.get('unit') or '',
               'qty': r.get('qty'), 'price': r.get('price'), 'total': r.get('total'),
               'order': r.get('order'), 'year': r.get('year')}
        if scope == 'all':
            out['sales'] = r.get('sales')
        base['records'].append(out)
    return base, 200


# ────────────────────────── HTTP 挂载点 ──────────────────────────
def handle_get(handler, au):
    """在 销售工作台服务.py 的 do_GET 里调用：price_api.handle_get(self, _au)"""
    qs = {k: v[0] for k, v in parse_qs(handler.path.split('?')[1]).items()} if '?' in handler.path else {}
    try:
        res, code = query(au, _nz(qs.get('sku')), _nz(qs.get('customer')),
                          year=_nz(qs.get('year')))
    except Exception as e:
        res, code = {'ok': False, 'error': '价格索引不可用：%s' % e}, 500
    handler._send_json(res, code)


# ────────────────────────── CLI（本地自测）──────────────────────────
def _cli():
    import argparse
    ap = argparse.ArgumentParser()
    ap.add_argument('--sku', default='')
    ap.add_argument('--customer', default='')
    ap.add_argument('--year', default='', help='2024/2025/2026，留空=全部')
    ap.add_argument('--user', default='刘新元')
    ap.add_argument('--role', default='sales')
    ap.add_argument('--limit', type=int, default=5)
    a = ap.parse_args()
    res, code = query({'name': a.user, 'role': a.role}, a.sku, a.customer, year=a.year)
    print('HTTP %d  scope=%s  year=%s' % (code, res.get('scope'), res.get('year')))
    if res.get('error'):
        print('error:', res['error'])
        return
    print('year_options:', json.dumps(res.get('year_options'), ensure_ascii=False))
    print('product:', json.dumps({k: res.get('product', {}).get(k) for k in
                                  ('sku', 'name', 'spec', 'cat', 'life_months', 'source', 'unlisted')},
                                 ensure_ascii=False))
    print('market :', json.dumps(res.get('market'), ensure_ascii=False))
    print('sku_by_year:', json.dumps(res.get('sku_by_year'), ensure_ascii=False))
    print('mine   :', json.dumps(res.get('mine'), ensure_ascii=False))
    print('by_cust:', json.dumps([{'c': r['customer'], 'last': r['last']['price'],
                                   'n': r['deal_count']} for r in res.get('by_customer', [])],
                                 ensure_ascii=False))
    print('records:', json.dumps(res.get('records', [])[:a.limit], ensure_ascii=False))
    print('customers named in response:',
          sorted(set(r['customer'] for r in res.get('records', [])))[:12])


if __name__ == '__main__':
    sys.exit(_cli())
