#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""报销助手 · 打印包生成（发票 + 配套材料，按制度粘贴顺序合并为一个 PDF）

制度粘贴顺序（CA-01-03《付款&费用报销单据粘贴要求》）：
  面单 → 差旅费报销汇总表/行驶记录明细表 → A5发票 → 行程单/住宿清单 → 小票 → 抵扣联

本脚本按「发票 → 其配套材料」的顺序合并，首页为批次清单（可当年面单要素填写参考）。

用法：
  /usr/bin/python3 脚本/build_print_pack.py                  # 全部发票
  /usr/bin/python3 脚本/build_print_pack.py --period 2026-09 # 指定报销期间
  /usr/bin/python3 脚本/build_print_pack.py --only-ok        # 只打「通过」的
  /usr/bin/python3 脚本/build_print_pack.py --ids INV-xxx,INV-yyy

输出：打印包/报销打印包_<期间>_<时间戳>.pdf，并写 数据/打印包.json（最新包信息，页面读取）
"""
import os
import sys
import json
import glob
import datetime

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import invoice_core as core  # noqa: E402

OUTDIR = os.path.join(core.BASE, '打印包')
PACKINFO = os.path.join(core.DATA, '打印包.json')
# ⚠️ 只能用 TTF/OTF：PyMuPDF 对 .ttc（Songti/PingFang）不能正确取字，会渲染成 ··· 乱码
FONTS = ['/System/Library/Fonts/Supplemental/Arial Unicode.ttf',
         '/Library/Fonts/Arial Unicode.ttf',
         '/System/Library/Fonts/Supplemental/Songti.ttc']


def pick_font():
    for f in FONTS:
        if os.path.exists(f):
            return f
    return None


def find_file(rec):
    """定位原始文件（归档目录优先，其次收件箱）"""
    name = rec.get('来源文件') or ''
    cands = [rec.get('来源文件归档'), name]
    for c in cands:
        if not c:
            continue
        for base in (core.BASE, core.INBOX, core.DONE):
            p = c if os.path.isabs(c) else os.path.join(base, c)
            if os.path.exists(p):
                return p
    for base in (core.DONE, core.INBOX):
        hit = glob.glob(os.path.join(base, '*%s*' % os.path.splitext(name)[0][:20]))
        if hit:
            return hit[0]
    return None


def cover_page(doc, rows, title, fontfile, period):
    """首页批次清单"""
    import fitz
    W, H = 420, 595          # A5
    page = doc.new_page(width=W, height=H)
    y = 46
    page.insert_text((40, y), '报销助手 · 打印包', fontsize=15, fontname='china-s')
    y += 26
    page.insert_text((40, y), title, fontsize=11, fontname='china-s')
    y += 18
    page.insert_text((40, y), '批次：%s ｜ 生成：%s' % (period or '全部', datetime.datetime.now().strftime('%Y-%m-%d %H:%M')),
                     fontsize=9, fontname='china-s')
    y += 26
    page.insert_text((40, y), '序号  开票日期     销方                    金额       配套', fontsize=8.5, fontname='china-s')
    y += 6
    page.draw_line(fitz.Point(40, y), fitz.Point(W - 40, y), width=0.6)
    y += 14
    total = 0.0
    for i, (inv, atts) in enumerate(rows, 1):
        amt = inv.get('价税合计') or 0
        total += float(amt or 0)
        line = '%2d   %-10s  %-20s %9.2f   %s' % (
            i, (inv.get('开票日期') or '')[:10],
            (inv.get('销方名称') or '')[:20], float(amt or 0),
            '、'.join(a.get('附件类别') or '附件' for a in atts) or '—')
        page.insert_text((40, y), line, fontsize=8, fontname='china-s')
        y += 13
        if y > H - 60:
            page = doc.new_page(width=W, height=H)
            y = 50
    y += 6
    page.draw_line(fitz.Point(40, y), fitz.Point(W - 40, y), width=0.6)
    y += 16
    page.insert_text((40, y), '合计 %.2f 元（共 %d 张发票）' % (total, len(rows)), fontsize=10, fontname='china-s')
    y += 20
    page.insert_text((40, y), '提示：按制度粘贴顺序——面单 → 汇总表 → A5发票 → 行程单/住宿清单 → 小票 → 抵扣联（不贴）',
                     fontsize=7.5, fontname='china-s')
    return total


def add_file(doc, path, label):
    """把 PDF/图片加入合并文档，返回页数或 None（不支持时）"""
    import fitz
    ext = os.path.splitext(path)[1].lower()
    if ext == '.pdf':
        try:
            src = fitz.open(path)
            doc.insert_pdf(src)
            n = len(src)
            src.close()
            return n
        except Exception as e:
            print('     ⚠️ PDF 读取失败 %s：%s' % (os.path.basename(path), str(e)[:50]))
            return None
    if ext in ('.png', '.jpg', '.jpeg', '.bmp', '.webp', '.tif', '.tiff'):
        try:
            img = fitz.open(path)
            rect = img[0].rect
            page = doc.new_page(width=rect.width, height=rect.height)
            page.insert_image(page.rect, filename=path)
            img.close()
            return 1
        except Exception as e:
            print('     ⚠️ 图片读取失败 %s：%s' % (os.path.basename(path), str(e)[:50]))
            return None
    return None


def main(argv):
    import fitz
    led = core.load_ledger()
    period = None
    if '--period' in argv:
        period = argv[argv.index('--period') + 1]
    only_ok = '--only-ok' in argv
    label = None
    if '--label' in argv:
        label = argv[argv.index('--label') + 1]
    ids = None
    if '--ids' in argv:
        ids = set(x.strip() for x in argv[argv.index('--ids') + 1].split(','))

    invs = [r for r in led['发票'] if r.get('类型') != '附件']
    atts = [r for r in led['发票'] if r.get('类型') == '附件']

    def atts_of(inv):
        mid = (inv.get('邮件') or {}).get('message_id') or ''
        num = str(inv.get('发票号码') or '')
        out = []
        for a in atts:
            am = (a.get('邮件') or {}).get('message_id') or ''
            an = a.get('来源文件') or ''
            if (mid and am == mid) or (num and len(num) >= 12 and num in an) \
               or (a.get('关联发票') and a.get('关联发票') == inv.get('id')):
                out.append(a)
        return out

    sel = []
    for r in invs:
        if ids and r.get('id') not in ids:
            continue
        if period and (r.get('报销期间') or (r.get('开票日期') or '')[:7]) != period:
            continue
        if only_ok and (r.get('校验') or {}).get('状态') != '通过':
            continue
        fp = find_file(r)
        if not fp:
            print('  ⏭ 跳过（找不到原始文件）：%s %s' % (r.get('id'), r.get('来源文件')))
            continue
        sel.append((r, atts_of(r), fp))

    if not sel:
        print('没有可打印的发票（当前台账 %d 张发票 / %d 份配套材料）' % (len(invs), len(atts)))
        print('提示：先抓取/拖入票据并跑 invoice_parse.py')
        return 0

    sel.sort(key=lambda x: ((x[0].get('开票日期') or ''), x[0].get('id') or ''))
    os.makedirs(OUTDIR, exist_ok=True)
    fontfile = pick_font()
    doc = fitz.open()
    if fontfile:
        rows = [(r, a) for r, a, _ in sel]
        total = cover_page(doc, rows, '本批次票据清单（面单参考）', fontfile, label or period)
    else:
        total = sum(float(r.get('价税合计') or 0) for r, _, _ in sel)

    print('合并打印包：%d 张发票（+配套材料）' % len(sel))
    added, skipped = 0, []
    for r, a_list, fp in sel:
        n1 = add_file(doc, fp, '发票')
        if n1 is None:
            skipped.append(fp)
            continue
        added += 1
        names = []
        for a in a_list:
            afp = find_file(a)
            if not afp:
                continue
            n2 = add_file(doc, afp, '配套')
            if n2:
                names.append('%s(%d页)' % (a.get('附件类别') or '附件', n2))
        print('  ✅ %-18s %-11s %9.2f  %s' % (r.get('id'), r.get('开票日期') or '', float(r.get('价税合计') or 0),
                                            ('+ ' + '、'.join(names)) if names else ''))

    if added == 0:
        print('⚠️ 没有成功加入任何票据（原始文件缺失或格式不支持）')
        return 1

    # ➕ 追加未关联到具体发票的配套材料（保证「都要打印」：结账单/行程单/清单/结算单）
    used = {a.get('id') for _, a_list, _ in sel for a in a_list}
    # 指定 --ids 时只打这批票的配套，不把其他批次的结账单/行程单全塞进来
    orphans = [] if (ids or only_ok) else [a for a in atts if a.get('id') not in used]
    _order = {'住宿清单': 0, '行程单': 1, '费用清单': 2, '结算单': 3}
    orphans.sort(key=lambda a: _order.get(a.get('附件类别'), 9))
    n_att = 0
    if orphans:
        font = pick_font()
        if font:
            import fitz
            W, H = 420, 595
            pg = doc.new_page(width=W, height=H)
            pg.insert_text((40, 52), '附：未关联到具体发票的配套材料', fontsize=13, fontname='china-s')
            pg.insert_text((40, 76), '（结账单／行程单／费用清单／结算单，请与对应发票一并粘贴）', fontsize=9, fontname='china-s')
            y = 106
            for i, a in enumerate(orphans, 1):
                amt = a.get('价税合计')
                pg.insert_text((40, y), '%2d  %-8s  %-30s %9s' % (
                    i, a.get('附件类别') or '附件', (a.get('来源文件') or '')[:30],
                    ('%.2f' % amt) if amt else ''), fontsize=8, fontname='china-s')
                y += 13
                if y > H - 50:
                    pg = doc.new_page(width=W, height=H)
                    y = 50
        for a in orphans:
            afp = find_file(a)
            if afp and add_file(doc, afp, '配套'):
                n_att += 1
        print('  ➕ 追加未关联配套材料 %d / %d 份' % (n_att, len(orphans)))

    stamp = datetime.datetime.now().strftime('%Y%m%d_%H%M')
    name = '报销打印包_%s_%s.pdf' % (label or period or '全部', stamp)
    out = os.path.join(OUTDIR, name)
    doc.save(out, garbage=3, deflate=True)
    npages = len(doc)
    doc.close()

    # OFD 等不支持格式单独提示
    for f in glob.glob(os.path.join(core.DONE, '*.ofd')) + glob.glob(os.path.join(core.INBOX, '*.ofd')):
        skipped.append(f)

    info = {'最新包': name, '路径': out, '生成时间': core.now(), '发票数': added,
            '配套材料数': n_att, '页数': npages, '合计': round(total, 2), '期间': period or '全部',
            '未纳入': [os.path.basename(x) for x in skipped]}
    json.dump(info, open(PACKINFO, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
    print('\n✅ 打印包已生成：%s' % out)
    print('   %d 张发票 + %d 份配套材料 ｜ %d 页 ｜ 合计 %.2f 元' % (added, n_att, npages, total))
    if skipped:
        print('   ⚠️ 未纳入 %d 个文件（OFD 无法直接打印，请另存为 PDF；或原始文件缺失）：%s'
              % (len(skipped), '、'.join(os.path.basename(x) for x in skipped[:6])))
    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
