#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""下载官网说明书 PDF 并抽取「体系配制表 / 推荐反应程序」原文块。

输入：数据/biori_official_catalog.json（由 crawl_biori_official.py 生成）
产出：
  资料/说明书/<文件名>.pdf          —— PDF 本体（交付时一起打包）
  数据/manual_text/<键>.json        —— 每份说明书的逐页文本 + 命中的体系/程序块（带页码，可溯源）

抽取策略（不生成、只摘录）：
  · 按页取文本；对每页用关键词定位「体系配制」「反应程序」「循环条件」等区块，
    命中后摘录该页对应行片段（保留原文与页码），不做改写、不做推测。
"""
import json
import os
import re
import sys
import time
import urllib.request

import fitz  # PyMuPDF

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
CAT = os.path.join(ROOT, '数据/biori_official_catalog.json')
PDF_DIR = os.path.join(ROOT, '资料/说明书')
TXT_DIR = os.path.join(ROOT, '数据/manual_text')
UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/122 Safari/537.36'

KW_SYS = ['体系配制', '反应体系', '配制体系', '体系组成', 'PCR体系', '反应液配制', '组分', '用量']
KW_PROG = ['反应程序', '循环条件', '扩增程序', '反应条件', '温度为', '预变性', '退火', '延伸', '循环数',
           '℃', 'min', 'sec']
MAX_MB = 20


def safe(name):
    return re.sub(r'[^\w\u4e00-\u9fff.\-]+', '_', name)[:80]


def download(url, path):
    req = urllib.request.Request(url, headers={'User-Agent': UA})
    with urllib.request.urlopen(req, timeout=60) as r:
        data = r.read()
    if len(data) > MAX_MB * 1024 * 1024:
        raise ValueError('文件过大 %.1fMB' % (len(data) / 1048576.0))
    open(path, 'wb').write(data)
    return len(data)


def extract(pdf_path):
    doc = fitz.open(pdf_path)
    pages, hits_sys, hits_prog, hits_sku = [], [], [], set()
    for i, pg in enumerate(doc, 1):
        t = pg.get_text('text')
        pages.append(t)
        for line in t.split('\n'):
            line = line.strip()
            if not line:
                continue
            if any(k in line for k in KW_SYS) and 3 <= len(line) <= 220:
                hits_sys.append({'页': i, '原文': line})
            if (any(k in line for k in KW_PROG) and 3 <= len(line) <= 220
                    and re.search(r'\d', line) and len(hits_prog) < 60):
                hits_prog.append({'页': i, '原文': line})
        for m in re.findall(r'\b(?:BR\d)?[A-Z]{1,4}\d{2,5}(?:-\d{2,6})?(?:-[A-Z0-9]{1,8})?\b', t):
            hits_sku.add(m)
    full = '\n'.join(pages)
    return {'页数': doc.page_count, '全文字符数': len(full),
            '体系配置候选': hits_sys[:40], '反应程序候选': hits_prog[:40],
            '文中货号': sorted(hits_sku)[:20], '逐页文本': pages}


def main():
    os.makedirs(PDF_DIR, exist_ok=True)
    os.makedirs(TXT_DIR, exist_ok=True)
    cat = json.load(open(CAT, encoding='utf-8'))
    items = [p for p in cat['items'] if p.get('说明书与资料')]
    print('目录产品 %d 个，其中带资料链接 %d 个' % (cat['产品数'], len(items)))
    ok = fail = skip = 0
    for i, p in enumerate(items, 1):
        url = p['说明书与资料'][0]['file_url']
        label = p['说明书与资料'][0].get('label') or p['官网品名']
        fname = safe(label)
        pdf = os.path.join(PDF_DIR, fname + '.pdf')
        js = os.path.join(TXT_DIR, fname + '.json')
        if os.path.exists(pdf) and os.path.exists(js):
            skip += 1
            continue
        try:
            if not os.path.exists(pdf):
                size = download(url, pdf)
            else:
                size = os.path.getsize(pdf)
            d = extract(pdf)
            d.update({'官网品名': p['官网品名'], '详情页': p['详情页'], '说明书URL': url,
                      '说明书文件名': label, 'PDF字节': size})
            json.dump(d, open(js, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
            ok += 1
            if ok % 20 == 0:
                print('  已处理 %d（成功 %d / 失败 %d）' % (i, ok, fail))
        except Exception as e:
            fail += 1
            print('  ✗ %s → %s: %s' % (fname[:40], type(e).__name__, str(e)[:80]))
        time.sleep(0.4)
    print('完成：成功 %d ｜ 跳过(已处理) %d ｜ 失败 %d' % (ok, skip, fail))
    total = sum(os.path.getsize(os.path.join(PDF_DIR, f)) for f in os.listdir(PDF_DIR))
    print('PDF 目录合计 %.1f MB ｜ %d 个文件' % (total / 1048576.0, len(os.listdir(PDF_DIR))))


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