#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
票面线索提取（归类用）
======================
对台账里的发票逐张读取「文本层 + OCR」，抽出归类需要的三类线索：
  ① 行程日期：铁路/航空票据里的实际乘车/乘机日期（≠开票日期，常滞后开票）
  ② 还车时间：租车结算单的实际还车时间（决定归到哪一行）
  ③ 销方名称：数电票版式导致解析失败时，用「公司名 + 18位税号」从票面重取
结果存 数据/_票面线索.json，并顺手修正台账里明显错误的销方名称。
"""
import glob
import json
import os
import re
import subprocess
import sys

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

OCR = core.OCR_BIN
CLUES = os.path.join(core.BASE, '数据', '_票面线索.json')
DATE_RE = re.compile(r'(20\d{2})\s*[年\-/]\s*(\d{1,2})\s*[月\-/]\s*(\d{1,2})')
CO_RE = re.compile(r'([\u4e00-\u9fa5]{3,24}(?:股份有限公司|有限责任公司|有限公司|酒店管理有限公司|酒店|宾馆|加油站|旅行社|航空|传媒))')
BAD = ('下载次数', '名称', '销方', '购方', '纳税人识别号')


def find_file(rec):
    name = rec.get('来源文件') or ''
    for base in (core.DONE, core.INBOX):
        p = os.path.join(base, name)
        if os.path.exists(p):
            return p
        hit = glob.glob(os.path.join(base, '*%s*' % os.path.splitext(name)[0][:24]))
        if hit:
            return hit[0]
    return None


def read_text(fp):
    try:
        doc = fitz.open(fp)
        layer = '\n'.join(p.get_text() for p in doc)
        doc.close()
    except Exception:
        layer = ''
    ocr = ''
    if os.path.exists(OCR) and len(layer) < 900:
        try:
            r = subprocess.run([OCR, fp], capture_output=True, timeout=150)
            ocr = r.stdout.decode('utf-8', 'ignore')
        except Exception:
            ocr = ''
    return re.sub(r'\s+', ' ', layer + ' ' + ocr)


def flat_dates(text):
    out = []
    for m in DATE_RE.finditer(text):
        try:
            import datetime
            out.append(datetime.date(int(m.group(1)), int(m.group(2)), int(m.group(3))))
        except ValueError:
            pass
    return out


def main(argv):
    only = [a for a in argv if not a.startswith('-')]
    led = core.load_ledger()
    invs = list(led['发票'])   # 含配套材料（租车结算单的还车时间要让发票侧用上）
    if only:
        invs = [r for r in invs if any(o in (r['id'] + ' ' + str(r.get('来源文件') or '')) for o in only)]
    clues, fixed = {}, 0
    for rec in invs:
        fp = find_file(rec)
        if not fp:
            continue
        text = read_text(fp)
        if not text:
            continue
        issue = str(rec.get('开票日期') or '')[:10]
        ds = [d for d in flat_dates(text) if d.isoformat() != issue]
        # ① 行程日期：与开票日期不同的、最近的过去日期
        travel = None
        if ds:
            cands = [d for d in ds if not issue or d.isoformat() < issue]
            travel = (max(cands) if cands else min(ds)).isoformat()
        # ② 还车时间
        rm = re.search(r'实际还车时间[^\d]{0,60}(20\d{2})[-\s年]*(\d{1,2})[-\s月]*(\d{1,2})', text)
        ret = '%s-%02d-%02d' % (rm.group(1), int(rm.group(2)), int(rm.group(3))) if rm else None
        # ③ 销方名称（公司名 + 18 位税号，取最后一次出现＝销售方）
        seller = None
        hits = [m for m in re.finditer(r'([\u4e00-\u9fa5]{4,24}(?:股份有限公司|有限责任公司|有限公司|酒店|宾馆|加油站|旅行社|传媒))\s*([0-9A-Z]{18})', text)]
        if hits:
            seller = hits[-1].group(1)
        hm = re.search(r'([\u4e00-\u9fa5]{2,20}(?:酒店|宾馆|公寓))', text)
        ci_ = re.search(r'入住日期\s*[:：]?\s*(20\d{2})[-\s]*(\d{1,2})[-\s]*(\d{1,2})', text)
        checkin = '%s-%02d-%02d' % (ci_.group(1), int(ci_.group(2)), int(ci_.group(3))) if ci_ else None
        clues[rec['id']] = {'text': text[:600], 'travel': travel, 'return': ret, 'seller': seller,
                            'hotel': hm.group(1) if hm else None, 'checkin': checkin}
        cur = str(rec.get('销方名称') or '')
        if seller and (not cur or any(b in cur for b in BAD)):
            rec['销方名称'] = seller
            fixed += 1
    core.save_ledger(led)
    # 合并写入：单张/子集运行绝不覆盖全量线索
    try:
        old_clues = json.load(open(CLUES, encoding='utf-8'))
    except Exception:
        old_clues = {}
    old_clues.update(clues)
    json.dump(old_clues, open(CLUES, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
    print('✅ 票面线索已提取：%d 张 ｜ 修正销方名称 %d 张' % (len(clues), fixed))
    print('   输出：%s' % CLUES)
    return 0


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