#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
按用户运行报销管道（服务端调用）
================================
解包 ZIP → 解析 → 校验 → 生成页面 →（可选）打印包
所有动作限定在该用户工作区内（BAOYU_EXPENSE_HOME），互不干扰。

用法：
  python3 run_user_pipeline.py --uid liuziyan [--pack] [--period 2026-09] [--json]

输出：最后一行 `__RESULT__{json}` 供服务端解析
"""
import argparse
import datetime
import glob
import json
import os
import shutil
import subprocess
import sys
import zipfile

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))   # 报销助手/
SCRIPTS = os.path.join(ROOT, '脚本')
sys.path.insert(0, SCRIPTS)

KEEP_EXT = ('.pdf',)          # 用户口径：只收 PDF（ZIP 内含 PDF）


def unzip_inbox(inbox):
    """把收件箱里的 ZIP 解开（含子目录），解完删除 ZIP 并记日志"""
    n = 0
    for zp in glob.glob(os.path.join(inbox, '*.zip')):
        try:
            with zipfile.ZipFile(zp) as z:
                for m in z.namelist():
                    if m.endswith('/') or '__MACOSX' in m:
                        continue
                    if not m.lower().endswith(KEEP_EXT):
                        continue
                    base = os.path.basename(m)
                    if not base or base.startswith('.'):
                        continue
                    target = os.path.join(inbox, base)
                    k = 1
                    while os.path.exists(target):
                        stem, ext = os.path.splitext(base)
                        target = os.path.join(inbox, '%s_%d%s' % (stem, k, ext))
                        k += 1
                    with z.open(m) as src, open(target, 'wb') as dst:
                        shutil.copyfileobj(src, dst)
                    n += 1
            os.remove(zp)
        except Exception as e:
            print('   ⚠️ ZIP 解包失败 %s: %s' % (os.path.basename(zp), e))
    return n


def run_step(script, env, extra=None):
    cmd = ['/usr/bin/python3', os.path.join(SCRIPTS, script)] + (extra or [])
    r = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=1800)
    tail = [l for l in (r.stdout or '').strip().split('\n') if l.strip()][-2:]
    return r.returncode, ' / '.join(tail), (r.stderr or '')[-300:]


def main(argv):
    ap = argparse.ArgumentParser()
    ap.add_argument('--uid', required=True)
    ap.add_argument('--pack', action='store_true', help='同时生成打印包')
    ap.add_argument('--period', default='')
    ap.add_argument('--json', action='store_true', help='只输出 JSON（服务端用）')
    a = ap.parse_args(argv)

    import expense_users as EU
    u = EU.get(a.uid)
    if not u:
        print('__RESULT__' + json.dumps({'ok': False, 'error': '未注册用户 %s' % a.uid}, ensure_ascii=False))
        return 2
    if not u.get('allow'):
        print('__RESULT__' + json.dumps({'ok': False, 'error': '未开放报销助手权限'}, ensure_ascii=False))
        return 3
    home = EU.ensure_workspace(u['uid']) if hasattr(EU, 'ensure_workspace') else EU.home_for(u['uid'])

    env = dict(os.environ)
    env['BAOYU_EXPENSE_HOME'] = home
    env['BAOYU_EXPENSE_SHARED'] = os.path.join(ROOT, '共享')
    env['PYTHONIOENCODING'] = 'utf-8'

    inbox = os.path.join(home, '数据', '收件箱')
    n_zip = unzip_inbox(inbox)

    steps = []
    for script in ('invoice_parse.py', 'invoice_check.py', 'build_page.py'):
        code, tail, err = run_step(script, env)
        steps.append({'step': script, 'code': code, 'tail': tail, 'err': err if code else ''})
        if code != 0 and script == 'invoice_parse.py':
            break
    pack_path = ''
    if a.pack:
        extra = ['--period', a.period] if a.period else []
        code, tail, err = run_step('build_print_pack.py', env, extra)
        steps.append({'step': 'build_print_pack.py', 'code': code, 'tail': tail, 'err': err if code else ''})
        info_f = os.path.join(home, '数据', '打印包.json')
        if os.path.exists(info_f):
            try:
                pack_path = json.load(open(info_f, encoding='utf-8')).get('路径', '')
            except Exception:
                pass

    # 汇总（读该用户台账）
    led_f = os.path.join(home, '数据', '报销台账.json')
    summary, counts = {}, {}
    if os.path.exists(led_f):
        try:
            led = json.load(open(led_f, encoding='utf-8'))
            summary = led.get('汇总') or {}
            for r in led.get('发票', []):
                if r.get('类型') == '附件':
                    continue
                st = (r.get('校验') or {}).get('状态') or '未校验'
                counts[st] = counts.get(st, 0) + 1
        except Exception as e:
            summary = {'error': str(e)}
    page = os.path.join(home, '工具', '报销助手.html')
    res = {'ok': all(s['code'] == 0 for s in steps if s['step'] != 'build_print_pack.py'),
           'uid': u['uid'], 'name': u.get('name', ''), 'unzipped': n_zip,
           '票据数': summary.get('票据数', 0), '合计': summary.get('合计', 0),
           '按状态': counts, 'steps': steps,
           '页面': page if os.path.exists(page) else '',
           '打印包': pack_path, '时间': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
    if not a.json:
        print('═══ 报销管道 · %s（%s）═══' % (u.get('name', ''), u['uid']))
        print('  ZIP 解包 %d 个文件 ｜ 工作区 %s' % (n_zip, home))
        for s in steps:
            print('  %-22s %s %s' % (s['step'], '✅' if s['code'] == 0 else '❌', s['tail'][:100]))
        print('  票据 %s 张 ｜ 合计 %s 元 ｜ 状态 %s' % (res['票据数'], res['合计'], counts))
        if pack_path:
            print('  打印包：%s' % pack_path)
    print('__RESULT__' + json.dumps(res, ensure_ascii=False))
    return 0


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