#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""销售日报推送：短卡片（KPI+今日关注） + 完整 HTML 附件 + CDN 链接
用法：python3 脚本/push_daily_brief_html.py [--dry-run] [--date YYYY-MM-DD]
凭据复用 ~/.hermes/scripts/push_daily_brief_card.py 中的常量（不在此文件复制密钥）
"""
import os, re, sys, json, glob, time, urllib.request, uuid, mimetypes

ROOT = os.path.expanduser('~/Desktop/Hermes输出-工作类')
MD = os.path.join(ROOT, '数据/daily_briefs/latest.md')
OUTDIR = os.path.join(ROOT, '数据/daily_briefs')
CRED_SRC = os.path.expanduser('~/.hermes/scripts/push_daily_brief_card.py')
CDN = 'https://bier-sales-d0gatbvlx288724e9-1458438710.tcloudbaseapp.com'
DRY = '--dry-run' in sys.argv
DATE_ARG = sys.argv[sys.argv.index('--date') + 1] if '--date' in sys.argv else None

# ── 凭据（从既有脚本读取，避免二次落盘）──────────────────────
src = open(CRED_SRC, encoding='utf-8').read()
APP_ID = re.search(r'FEISHU_APP_ID\s*=\s*"([^"]+)"', src).group(1)
APP_SECRET = re.search(r'FEISHU_APP_SECRET\s*=\s*"([^"]+)"', src).group(1)
OPEN_ID = re.search(r'OPEN_ID\s*=\s*"([^"]+)"', src).group(1)

raw = open(MD, encoding='utf-8').read()
lines = [l.rstrip('\n') for l in raw.split('\n')]


def grab(pat, d='—'):
    m = re.search(pat, raw)
    if not m:
        return d
    return (m.group(1) if m.groups() else m.group(0))


REPORT_DATE = DATE_ARG
if not REPORT_DATE:
    m = re.search(r'(\d{4}-\d{2}-\d{2})（周', raw)
    REPORT_DATE = m.group(1) if m else time.strftime('%Y-%m-%d')

HTML = os.path.join(OUTDIR, '宝锐销售日报_%s.html' % REPORT_DATE)
if not os.path.exists(HTML):
    print('⚠️ 未找到 HTML：%s' % HTML); sys.exit(2)

stats = {'act': grab(r'活动\s*(\d+)\s*条'), 'cmp': grab(r'未关闭客诉\s*(\d+)\s*条'),
         'ord': grab(r'近7天\s*(\d+)\s*单'), 'amt': grab(r'¥\s*([\d.]+)\s*万')}
try:
    import json as _j
    erp = _j.load(open(os.path.join(ROOT, '数据/erp_amount.json'), encoding='utf-8'))
    mm = max((erp.get('monthly') or {}).keys()) if erp.get('monthly') else ''
    m_amt = float((erp.get('monthly') or {}).get(mm) or 0) / 10000
except Exception:
    mm, m_amt = '', 0.0
try:
    comp = _j.load(open(os.path.join(ROOT, '数据/mcp_complaints.json'), encoding='utf-8'))
    items = comp.get('items') if isinstance(comp, dict) else comp
    over30 = sum(1 for c in (items or []) if str(c.get('status')) not in ('结束', '已关闭')
                 and int(c.get('days') or 0) > 30)
except Exception:
    over30 = 0

# 今日关注前 6 条
focus = []
started = False
for l in lines:
    s = l.strip()
    if s.startswith('⚠️') and '关注' in s:
        started = True; continue
    if started:
        if s.startswith(('👥', '📊', '⚔️', '🚀', '💰')) or not s:
            break
        if s.startswith('·'):
            t = s.lstrip('· ').strip()
            t = re.sub(r'\[([^\]]*)\]\((https?://[^)]+)\)', lambda m: '[%s](%s)' % (m.group(1), m.group(2)), t)
            focus.append(t)
TOP = focus[:6]

URL = '%s/daily/%s.html' % (CDN, REPORT_DATE)
SIZE_KB = os.path.getsize(HTML) / 1024

# 全量条数（从 HTML 里读，用于卡片文案）
h = open(HTML, encoding='utf-8').read()
def cnt(pat):
    m = re.search(pat, h)
    return m.group(1) if m else '?'
n_green = cnt(r'重点进展 <em>(\d+) 条')
n_red = cnt(r'风险/受阻（\d+条）</h2>')
n_red2 = cnt(r'风险/受阻[^<]*<em>(\d+) 条')

card = {
    'config': {'wide_screen_mode': True},
    'header': {'title': {'tag': 'plain_text', 'content': '📋 销售日报 %s' % REPORT_DATE}, 'template': 'blue'},
    'elements': [
        {'tag': 'column_set', 'flex_mode': 'none', 'background_style': 'grey', 'columns': [
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**%s**\n昨日活动' % stats['act']}}]},
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**%s**\n未关闭客诉' % stats['cmp']}}]},
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**%s**\n近7天订单' % stats['ord']}}]},
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**¥%s万**\n近7天金额' % stats['amt']}}]},
        ]},
        {'tag': 'column_set', 'flex_mode': 'none', 'background_style': 'grey', 'columns': [
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**¥%.1f万**\n%s 累计' % (m_amt, mm or '本月')}}]},
            {'tag': 'column', 'width': 'weighted', 'weight': 1, 'elements': [
                {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**%d**\n超30天未闭环' % over30}}]},
        ]},
        {'tag': 'hr'},
        {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**⚠️ 今日关注（前 6 条）**'}},
    ] + [{'tag': 'div', 'text': {'tag': 'lark_md', 'content': '· ' + t}} for t in TOP] + [
        {'tag': 'div', 'text': {'tag': 'lark_md', 'content': '**📄 完整版见附件 / 点下面按钮**（含全部条目，不再截断）'}},
        {'tag': 'action', 'actions': [
            {'tag': 'button', 'text': {'tag': 'plain_text', 'content': '📄 打开完整日报（HTML）'},
             'type': 'primary', 'url': URL},
            {'tag': 'button', 'text': {'tag': 'plain_text', 'content': '🗂 历史日报归档'},
             'url': '%s/daily/index.html' % CDN},
        ]},
        {'tag': 'note', 'elements': [{'tag': 'plain_text',
                                      'content': '🤖 宝锐工作台 · 日报（HTML 完整版 %d KB，附件可下载）' % SIZE_KB}]},
    ],
}
cb = len(json.dumps(card, ensure_ascii=False).encode('utf-8'))
print('卡片 JSON %d 字节 ｜ 关注 %d 条 ｜ 完整版 %d KB' % (cb, len(TOP), SIZE_KB))
print('链接: %s' % URL)
if DRY:
    print('（--dry-run：不发送）')
    sys.exit(0)


def api(path, data=None, token=None, method='POST', headers=None, timeout=30, label=''):
    req = urllib.request.Request('https://open.feishu.cn' + path, data=data, method=method,
                                 headers=headers or {})
    if token:
        req.add_header('Authorization', 'Bearer ' + token)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        body = ''
        try:
            body = e.read().decode('utf-8', 'ignore')[:400]
        except Exception:
            pass
        print('❌ HTTP %s [%s] %s → %s' % (e.code, label or path, e.reason, body))
        return {'code': -1, '_http': e.code, '_body': body}


tok = api('/open-apis/auth/v3/tenant_access_token/internal',
          json.dumps({'app_id': APP_ID, 'app_secret': APP_SECRET}).encode(),
          headers={'Content-Type': 'application/json'}, label='token')
if tok.get('code') != 0:
    print('❌ token 失败'); sys.exit(1)
T = tok['tenant_access_token']

# 1) 发卡片
res = api('/open-apis/im/v1/messages?receive_id_type=open_id',
          json.dumps({'receive_id': OPEN_ID, 'msg_type': 'interactive',
                      'content': json.dumps(card, ensure_ascii=False)}).encode('utf-8'),
          token=T, headers={'Content-Type': 'application/json; charset=utf-8'}, label='card')
mid = (res.get('data') or {}).get('message_id')
print('卡片推送: %s  message_id=%s' % ('OK' if res.get('code') == 0 else 'FAIL ' + json.dumps(res, ensure_ascii=False)[:200], mid))

# 2) 上传 HTML 附件并发送
boundary = '----hermes' + uuid.uuid4().hex
fname = os.path.basename(HTML).encode('utf-8').decode('utf-8')
body = []
def part(name, value, filename=None, ctype='text/plain'):
    head = '--%s\r\nContent-Disposition: form-data; name="%s"' % (boundary, name)
    if filename:
        head += '; filename="%s"' % filename
    head += '\r\nContent-Type: %s\r\n\r\n' % ctype
    return head.encode('utf-8') + (value if isinstance(value, bytes) else value.encode('utf-8')) + b'\r\n'
body.append(part('file_type', 'stream'))
body.append(part('file_name', fname))
body.append(part('file', open(HTML, 'rb').read(), filename=fname, ctype='text/html'))
payload = b''.join(body) + ('--%s--\r\n' % boundary).encode()
up = api('/open-apis/im/v1/files', payload, token=T,
         headers={'Content-Type': 'multipart/form-data; boundary=' + boundary}, timeout=90, label='upload')
fk = (up.get('data') or {}).get('file_key')
up_ok = up.get('code') == 0
print('附件上传: %s  file_key=%s' % ('OK' if up_ok else 'FAIL', fk))
if not up_ok:
    print('   ↳ 附件未送达原因（飞书应用缺权限），请在开放平台为该应用开通 im:resource:upload：')
    print('   ↳ https://open.feishu.cn/app/cli_aaee6a2f90b89bc1/auth?q=im:resource:upload,im:resource&op_from=openapi&token_type=tenant')
    print('   ↳ 未开通也不影响日报：卡片按钮指向 CDN 完整版 HTML（不截断）')
if fk:
    r2 = api('/open-apis/im/v1/messages?receive_id_type=open_id',
             json.dumps({'receive_id': OPEN_ID, 'msg_type': 'file',
                         'content': json.dumps({'file_key': fk})}).encode('utf-8'),
             token=T, headers={'Content-Type': 'application/json; charset=utf-8'}, label='filemsg')
    print('附件推送: %s  message_id=%s' % ('OK' if r2.get('code') == 0 else 'FAIL ' + json.dumps(r2, ensure_ascii=False)[:200],
                                        (r2.get('data') or {}).get('message_id')))

# 3) 回读验证卡片内容
if mid:
    time.sleep(2)
    chk = api('/open-apis/im/v1/messages/%s' % mid, token=T, method='GET')
    items = ((chk.get('data') or {}).get('items') or [{}])[0]
    bodytxt = json.dumps(items.get('body') or {}, ensure_ascii=False)
    print('回读验证: code=%s ｜ 正文含 KPI=%s ｜ 含首条关注=%s'
          % (chk.get('code'), ('未关闭客诉' in bodytxt),
             (TOP[0][:12] in bodytxt) if TOP else 'n/a'))

# 卡片已送达即成功（附件权限缺失不阻断日报）
print('DONE card_ok=%s attach_ok=%s' % (bool(mid), bool(fk)))
sys.exit(0 if mid else 1)
