"""
2020 年版药典 PDF 导入脚本
解析 pharmacopoeia_2020_volume1_toc.pdf → 提取药品条目 → 向量化 → 写入 PostgreSQL
"""
import json, os, sys, re, hashlib, asyncio
from pathlib import Path
from collections import OrderedDict

import httpx
import fitz  # pymupdf
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
from processors.chunker import DrugChunker

# ============================================
# 配置
# ============================================
PDF_PATH = os.environ.get("PDF_2020_PATH", os.path.join(
    os.path.dirname(__file__), "..", "data", "pharmacopoeia_2020_volume1_toc.pdf"))

PG_HOST = os.environ.get("POSTGRES_HOST", "localhost")
PG_PORT = os.environ.get("POSTGRES_PORT", "5432")
PG_DB = os.environ.get("POSTGRES_DB", "pharmacopoeia")
PG_USER = os.environ.get("POSTGRES_USER", "postgres")
PG_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
DB_URL = f"postgresql+asyncpg://{PG_USER}:{PG_PASSWORD}@{PG_HOST}:{PG_PORT}/{PG_DB}"

QWEN_API_KEY = os.environ.get("QWEN_API_KEY", "")
EMBEDDING_URL = "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"
EMBEDDING_MODEL = "text-embedding-v3"
BATCH_SIZE = 10

# 2020 药典 section 标题识别
SECTION_HEADERS = OrderedDict([
    ("性状", "性状"), ("鉴别", "鉴别"), ("检查", "检查"),
    ("含量测定", "含量测定"), ("浸出物", "浸出物"),
    ("性味与归经", "性味与归经"), ("性味", "性味与归经"),
    ("功能与主治", "功能主治"), ("功能", "功能主治"), ("主治", "功能主治"),
    ("用法与用量", "用法用量"), ("用法", "用法用量"), ("用量", "用法用量"),
    ("注意", "注意事项"), ("注意事项", "注意事项"),
    ("规格", "规格"), ("贮藏", "贮藏"), ("类别", "类别"),
    ("制剂", "制剂"), ("附注", "附注"), ("禁忌", "禁忌"),
    ("不良反应", "不良反应"),
    ("处方", "处方"), ("制法", "制法"),
    ("包装", "包装"), ("有效期", "有效期"),
    ("执行标准", "执行标准"), ("批准文号", "批准文号"),
])


def load_env():
    env_file = Path(__file__).resolve().parent.parent / ".env"
    if env_file.exists():
        for line in open(env_file, encoding="utf-8"):
            line = line.strip()
            if line and not line.startswith("#") and "=" in line:
                key, _, val = line.partition("=")
                os.environ.setdefault(key.strip(), val.strip())


def is_cjk_name(s):
    """药名检测：2-10 字符，CJK 占 70% 以上，允许破折号/连接符/括号"""
    if not s or len(s) < 2 or len(s) > 10:
        return False
    # Skip pure digits/punctuation
    if re.match(r'^[\d\s\.\,\;\:\!\?\(\)\[\]\{\}<>@#$%^&*+=|\\/\"\']+$', s):
        return False
    cjk = sum(1 for c in s if ord(c) >= 0x4E00 and ord(c) <= 0x9FFF)
    return cjk >= 2 and cjk >= len(s) * 0.7


def is_pinyin_line(s):
    """拼音行：纯拉丁字母空格，首字母大写"""
    if not s or len(s) < 3 or len(s) > 80:
        return False
    alpha_spaces = sum(1 for c in s if c.isalpha() or c.isspace())
    return alpha_spaces == len(s) and any(c.isalpha() for c in s) and s[0].isupper()


def extract_drug_entries(pdf_path: str) -> list[dict]:
    """解析策略：扫描【性状】锚点，向前找药名。
    药名特征：2-6 纯 CJK + 下一行是拼音（首字母大写）或全大写拉丁名。
    只有通过拼音/拉丁名确认的才作为药名候选。
    """
    doc = fitz.open(pdf_path)
    print(f"PDF: {doc.page_count} pages")

    # 跳过前言（前 50 页）
    all_text = ""
    for i in range(32, doc.page_count):
        all_text += doc[i].get_text() + "\n"
    doc.close()

    lines = all_text.split('\n')
    clean_lines = []
    for line in lines:
        s = line.strip()
        if not s: continue
        if s.startswith("中国药典") and len(s) < 30: continue
        if "ISBN" in s or "CIP" in s: continue
        if re.match(r'^[・•·]\s*\d+\s*[・•·]$', s): continue
        clean_lines.append(s)

    # 第一遍：标记所有"已确认的药名"
    # 方式1：CJK + 下一行是拼音或拉丁名（药材和饮片）
    # 方式2：CJK + 下一行是【处方】或【制法】（成方制剂，无拼音）
    confirmed_names = set()
    for i in range(len(clean_lines) - 2):
        s = clean_lines[i]
        nxt = clean_lines[i + 1]
        if is_cjk_name(s):
            if is_pinyin_line(nxt) or (nxt.isupper() and len(nxt) > 5):
                confirmed_names.add(s)
            elif nxt.startswith('【处方】') or nxt.startswith('【制法】'):
                confirmed_names.add(s)

    print(f"   确认药名: {len(confirmed_names)} 个")

    # 第二遍：以已确认药名为锚点，提取药品条目
    entries = []
    current_drug = None
    current_sections = OrderedDict()
    current_section = "正文"
    current_text = []
    in_drug = False

    for s in clean_lines:
        # 检测 section
        m = re.match(r'^【(.+?)】\s*(.*)', s)
        if m:
            sec_raw = m.group(1)
            sec_content = m.group(2)
            section_found = None
            for key, std_name in SECTION_HEADERS.items():
                if key in sec_raw:
                    section_found = std_name
                    break

            if section_found and in_drug:
                if current_text:
                    current_sections[current_section] = '\n'.join(current_text).strip()
                    current_text = []
                current_section = section_found
                if sec_content:
                    current_text.append(sec_content)
                continue

        # 检测已确认的药名
        if s in confirmed_names:
            # 保存上一个药品
            if in_drug and current_drug and current_sections:
                if current_text:
                    current_sections[current_section] = '\n'.join(current_text).strip()
                entries.append({"name": current_drug, "sections": dict(current_sections)})
            # 开始新药品
            current_drug = s
            in_drug = True
            current_sections = OrderedDict()
            current_section = "正文"
            current_text = []
            # 描述段落（药名和【性状】之间的文本）归入"正文"
            continue

        # 跳过拼音行和拉丁名行
        if in_drug and not current_sections and (is_pinyin_line(s) or (s.isupper() and len(s) > 5)):
            continue

        # 正文内容
        if in_drug:
            current_text.append(s)

    # 最后一个
    if in_drug and current_drug and current_sections:
        if current_text:
            current_sections[current_section] = '\n'.join(current_text).strip()
        entries.append({"name": current_drug, "sections": dict(current_sections)})

    print(f"   初步提取: {len(entries)} 个条目")

    # 去重：同一药名保留内容最多的条目
    best = {}
    for e in entries:
        content_len = sum(len(v) for v in e["sections"].values())
        if len(e["sections"]) >= 1 and content_len > 50:
            name = e["name"]
            if name not in best or content_len > sum(len(v) for v in best[name]["sections"].values()):
                hash_suffix = hashlib.md5(name.encode()).hexdigest()[:6].upper()
                e["drug_id"] = f"Z2020-{hash_suffix}"
                e["category"] = "中药"
                e["subcategory"] = ""
                e["name_en"] = ""
                e["pinyin"] = ""
                e["source"] = {"version": "2020年版", "volume": "一部", "page": ""}
                best[name] = e

    return list(best.values())


async def get_embeddings(texts: list[str], text_type: str = "document") -> list[list[float]]:
    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(
            EMBEDDING_URL,
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {QWEN_API_KEY}",
            },
            json={
                "model": EMBEDDING_MODEL,
                "input": {"texts": texts},
                "parameters": {"text_type": text_type},
            },
        )
        data = resp.json()
    if data.get("code") and data.get("code") != "" and data.get("code") is not None:
        raise RuntimeError(f"Embedding error: {data.get('message', data)}")
    return [item["embedding"] for item in data["output"]["embeddings"]]


async def ingest_entries(entries: list[dict]):
    """向量化 + 入库。长 section 用 DrugChunker 切分。"""
    engine = create_async_engine(DB_URL)
    chunker = DrugChunker(chunk_size=1500, chunk_overlap=200, min_chunk_size=200)

    chunks = []
    chunk_meta = []

    for entry in entries:
        source = f"{entry['source']['version']} {entry['source']['volume']}"
        drug_name = entry["name"]
        for section_key, section_text in entry["sections"].items():
            if not section_text or len(section_text.strip()) < 5:
                continue

            if len(section_text) <= 1500:
                content = f"【{drug_name} - {section_key}】\n{section_text}\n\n来源：{source}"
                chunks.append(content)
                chunk_meta.append({
                    "drug_id": entry["drug_id"],
                    "section": section_key,
                    "content": content,
                    "source": source,
                })
            else:
                source_dict = {"version": entry["source"]["version"],
                              "volume": entry["source"]["volume"],
                              "page": entry["source"].get("page", "")}
                sub_chunks = chunker._split_long_section(section_text, drug_name, section_key, source_dict)
                for sub in sub_chunks:
                    full_content = sub.content + f"\n\n来源：{source}"
                    chunks.append(full_content)
                    chunk_meta.append({
                        "drug_id": entry["drug_id"],
                        "section": section_key,
                        "content": full_content,
                        "source": source,
                    })

    print(f"   {len(chunks)} chunks，向量化中...")

    all_vectors = []
    for i in range(0, len(chunks), BATCH_SIZE):
        batch = chunks[i:i + BATCH_SIZE]
        vecs = await get_embeddings(batch, text_type="document")
        all_vectors.extend(vecs)
        n = min(i + BATCH_SIZE, len(chunks))
        print(f"    向量化: {n}/{len(chunks)}")

    print(f"  [OK] 向量化完成, 维度={len(all_vectors[0]) if all_vectors else 'N/A'}")

    # 写入 drug_chunks
    chunk_count = 0
    async with engine.begin() as conn:
        for idx, (meta, vec) in enumerate(zip(chunk_meta, all_vectors)):
            vec_str = f"[{','.join(str(v) for v in vec)}]"
            await conn.execute(
                text("""
                    INSERT INTO drug_chunks (drug_id, section, content, source, chunk_index, embedding, vec)
                    VALUES (:drug_id, :section, :content, :source, :chunk_index, :embedding, :vec)
                    ON CONFLICT DO NOTHING
                """),
                {
                    "drug_id": meta["drug_id"],
                    "section": meta["section"],
                    "content": meta["content"],
                    "source": meta["source"],
                    "chunk_index": idx,
                    "embedding": json.dumps(vec),
                    "vec": vec_str,
                },
            )
            chunk_count += 1

    # 写入 drugs 表
    drug_count = 0
    async with engine.begin() as conn:
        for entry in entries:
            await conn.execute(
                text("""
                    INSERT INTO drugs (drug_id, name, name_en, pinyin, category, subcategory,
                                       sections, source_version, source_volume, source_page, is_active)
                    VALUES (:drug_id, :name, :name_en, :pinyin, :category, :subcategory,
                            :sections, :source_version, :source_volume, :source_page, TRUE)
                    ON CONFLICT (drug_id) DO UPDATE SET
                        sections = EXCLUDED.sections,
                        updated_at = NOW()
                """),
                {
                    "drug_id": entry["drug_id"],
                    "name": entry["name"],
                    "name_en": entry.get("name_en", ""),
                    "pinyin": entry.get("pinyin", ""),
                    "category": entry.get("category", ""),
                    "subcategory": entry.get("subcategory", ""),
                    "sections": json.dumps(entry.get("sections", {}), ensure_ascii=False),
                    "source_version": entry["source"]["version"],
                    "source_volume": entry["source"]["volume"],
                    "source_page": entry["source"].get("page", ""),
                },
            )
            drug_count += 1

    await engine.dispose()
    print(f"\n[DONE] 2020 药典入库完成！药品 {drug_count} 个，chunk {chunk_count} 条")


async def main():
    load_env()
    global QWEN_API_KEY
    QWEN_API_KEY = os.environ.get("QWEN_API_KEY", "")
    if not QWEN_API_KEY:
        print("❌ 未设置 QWEN_API_KEY")
        sys.exit(1)

    print("=" * 60)
    print("解析 2020 年版药典 PDF...")
    entries = extract_drug_entries(PDF_PATH)
    print(f"   提取药品条目: {len(entries)}")
    print("=" * 60)

    if not entries:
        print("❌ 未提取到有效条目，请检查 PDF 解析逻辑")
        return

    # 打印前 5 个条目作为样品
    print("\n前 5 个条目预览:")
    for e in entries[:5]:
        secs = list(e["sections"].keys())
        total_len = sum(len(v) for v in e["sections"].values())
        print(f"   {e['name']} | {len(secs)} sections ({total_len} 字) | {secs}")

    print(f"\n[>] 开始向量化入库...")
    await ingest_entries(entries)


if __name__ == "__main__":
    asyncio.run(main())
