"""
维基百科药品数据采集器
从中文维基百科采集药品条目，输出标准 JSON 格式

特性：
- 频率控制：每次请求间隔 3 秒（尊重维基百科服务器）
- 批次处理：每 20 个药品暂停 60 秒
- 断点续采：已采集的自动跳过
- 输出符合 drug_schema.json 规范
"""
import json
import re
import time
import sys
import os
from pathlib import Path
from urllib.parse import quote

import httpx
from bs4 import BeautifulSoup

# ============================================
# 配置
# ============================================
REQUEST_INTERVAL = 3.0       # 单次请求间隔（秒）
BATCH_SIZE = 20              # 每批采集数量
BATCH_PAUSE = 60              # 批次间暂停（秒）
WIKI_BASE = "https://zh.wikipedia.org/wiki/"
HEADERS = {
    "User-Agent": "PharmacopoeiaAI/1.0 (Research bot; contact@example.com) Mozilla/5.0",
    "Accept-Language": "zh-CN,zh;q=0.9",
}

# 匹配药典相关 section 标题
SECTION_PATTERNS = {
    "性状": re.compile(r"(性状|外观|物理性质|Description|Appearance)"),
    "适应症": re.compile(r"(适应症|适应征|主治|功能主治|Indications)"),
    "用法与用量": re.compile(r"(用法|用量|剂量|给药|Dosage|Administration)"),
    "禁忌": re.compile(r"(禁忌|禁用|Contraindications)"),
    "不良反应": re.compile(r"(不良反|副作用|Side.effect|Adverse)"),
    "注意事项": re.compile(r"(注意|警告|谨慎|Precautions|Warnings)"),
    "贮藏": re.compile(r"(贮藏|储存|保存|Storage)"),
    "药理": re.compile(r"(药理|药效|作用机制|Pharmacology|Mechanism)"),
}

# 内容长度限制（避免单 section 过长超出 LLM 上下文窗口）
MAX_OVERVIEW_LENGTH = 5000
MAX_SECTION_LENGTH = 8000


def fetch_wiki_page(drug_name: str, client: httpx.Client) -> tuple[int, str]:
    """获取维基百科页面"""
    url = WIKI_BASE + quote(drug_name)
    resp = client.get(url, headers=HEADERS, follow_redirects=True)
    # 检查是否重定向到非药品页面（如消歧义页）
    if resp.status_code == 200:
        text = resp.text
        if "维基百科目前还没有" in text or "Wikipedia does not have" in text:
            return 404, ""
    return resp.status_code, resp.text


def parse_drug_sections(html: str, drug_name: str) -> dict:
    """解析维基百科页面，提取药品相关信息"""
    soup = BeautifulSoup(html, "lxml")
    
    # 提取基本信息
    infobox = {}
    info_table = soup.find("table", class_="infobox")
    if info_table:
        for row in info_table.find_all("tr"):
            th = row.find("th")
            td = row.find("td")
            if th and td:
                key = th.get_text(strip=True)
                val = td.get_text(" ", strip=True)
                infobox[key] = val

    # 提取正文内容（跳过导航、引用等）
    content = soup.find("div", class_="mw-parser-output")
    if not content:
        return {"sections": {}, "infobox": infobox}

    sections = {}
    current_section = "概述"
    current_text = []
    overview_text = []

    # 提取开头概述（第一个标题之前的内容）
    for elem in content.children:
        # 找第一个 h2 之前的段落
        if elem.name in ("h2", "h3", "h4"):
            # 保存之前的内容
            if overview_text:
                sections["概述"] = " ".join(overview_text)[:MAX_OVERVIEW_LENGTH]
                overview_text = []
            break
        if elem.name == "p" or elem.name == "div":
            t = elem.get_text(" ", strip=True)
            if len(t) > 20:
                overview_text.append(t)

    if overview_text:
        sections["概述"] = " ".join(overview_text)[:MAX_OVERVIEW_LENGTH]

    # 提取各 section
    for elem in content.children:
        if elem.name in ("h2", "h3", "h4"):
            if current_text and current_section:
                matched = None
                for key, pattern in SECTION_PATTERNS.items():
                    if pattern.search(current_section):
                        matched = key
                        break
                if matched:
                    sections[matched] = " ".join(current_text)[:MAX_SECTION_LENGTH]
                else:
                    sections[current_section] = " ".join(current_text)[:MAX_SECTION_LENGTH]
            current_section = elem.get_text(strip=True).replace("[编辑]", "").strip()
            current_text = []
            continue

        if elem.name in ("p", "ul", "ol", "div"):
            t = elem.get_text(" ", strip=True)
            if len(t) > 10:
                current_text.append(t)

    # 保存最后一个 section
    if current_text and current_section:
        matched = None
        for key, pattern in SECTION_PATTERNS.items():
            if pattern.search(current_section):
                matched = key
                break
        if matched:
            sections[matched] = " ".join(current_text)[:MAX_SECTION_LENGTH]
        else:
            sections[current_section] = " ".join(current_text)[:MAX_SECTION_LENGTH]

    return {
        "sections": sections,
        "infobox": infobox,
    }


def wiki_to_drug_entry(drug_name: str, parsed: dict) -> dict:
    """转换为标准 drug entry JSON"""
    infobox = parsed.get("infobox", {})
    sections = parsed.get("sections", {})

    return {
        "drug_id": f"WIKI-{drug_name[:30]}",
        "name": drug_name,
        "name_en": infobox.get("其他名称", ""),
        "pinyin": "",
        "category": infer_category(drug_name, sections),
        "subcategory": "",
        "sections": sections,
        "source": {
            "version": "维基百科",
            "volume": f"https://zh.wikipedia.org/wiki/{quote(drug_name)}",
            "page": "线上条目",
        },
    }


def infer_category(name: str, sections: dict) -> str:
    """推断药品分类（基于名称和正文内容综合判断）"""
    all_text = " ".join(sections.values()) + name

    # 生物制品特征词（优先级最高）
    bio_markers = ["疫苗", "毒素", "抗血清", "免疫球蛋白", "单克隆抗体", "重组", "基因工程"]
    if any(k in all_text for k in bio_markers):
        return "生物制品"

    # 中药特征：多个特征同时出现才判定为中药，减少误判
    tcm_name_markers = ["丸", "散", "汤", "丹", "膏", "颗粒", "胶囊"]
    tcm_content_markers = ["中药", "本草", "性味", "归经", "炮制", "饮片"]
    tcm_herb_chars = ["草", "花", "叶", "根", "皮", "参", "芪", "苓", "术", "芍", "芷"]

    # 检查药品名是否含多个中药特征
    name_tcm_score = sum(1 for k in tcm_name_markers if k in name)
    name_tcm_score += sum(1 for c in tcm_herb_chars if c in name)

    # 检查正文是否含中药特征
    content_tcm = any(k in all_text for k in tcm_content_markers)

    # 名称含中药剂型 + 至少 1 个其他特征 → 中药
    if name_tcm_score >= 2 or (name_tcm_score >= 1 and content_tcm):
        return "中药"

    return "化学药"


def main():
    # 读取药品索引
    index_path = Path(__file__).resolve().parent.parent / "data" / "drug_index.json"
    with open(index_path, "r") as f:
        drug_list = json.load(f)

    # 输出目录
    output_dir = Path(__file__).resolve().parent.parent / "data" / "wiki_drugs"
    output_dir.mkdir(parents=True, exist_ok=True)

    # 已采集的跳过（断点续采）
    already = set(f.stem for f in output_dir.glob("*.json"))

    # 只采集化学药 + 生物制品（跳过中药、通则需要更复杂的解析）
    candidates = [d for d in drug_list if d["volume"] in (2, 3)]
    
    print(f"📋 候选药品: {len(candidates)} 个（二部化学药 + 三部生物制品）")
    print(f"   已采集: {len(already)} 个")
    print(f"   待采集: {len(candidates) - len(already)} 个")
    print(f"   频率控制: {REQUEST_INTERVAL}s/次, 每{BATCH_SIZE}次暂停{BATCH_PAUSE}s")
    print(f"   预计耗时: ~{(len(candidates) - len(already)) * (REQUEST_INTERVAL + 1) / 60:.0f} 分钟\n")

    client = httpx.Client(timeout=20, follow_redirects=True)
    count = 0
    success = 0
    failed = 0

    for drug in candidates:
        name = drug["name"]
        safe_name = name.replace("/", "_").replace(":", "_")

        if safe_name in already:
            continue

        count += 1
        status, html = fetch_wiki_page(name, client)
        time.sleep(REQUEST_INTERVAL)

        if status != 200 or not html:
            print(f"  [{count}] ❌ {name} (HTTP {status})")
            failed += 1
        else:
            try:
                parsed = parse_drug_sections(html, name)
                entry = wiki_to_drug_entry(name, parsed)

                # 至少有一个 section 才算有效
                sections_count = len(entry["sections"])
                if sections_count < 1:
                    print(f"  [{count}] ⚠️  {name} (无有效内容)")
                    failed += 1
                    continue

                out_path = output_dir / f"{safe_name}.json"
                with open(out_path, "w", encoding="utf-8") as f:
                    json.dump(entry, f, ensure_ascii=False, indent=2)

                print(f"  [{count}] ✅ {name} ({sections_count} sections)")
                success += 1
            except Exception as e:
                print(f"  [{count}] ❌ {name} (解析失败: {str(e)[:50]})")
                failed += 1

        # 批次间暂停
        if count % BATCH_SIZE == 0 and count > 0:
            print(f"\n⏸  已处理 {count} 个，暂停 {BATCH_PAUSE}s...\n")
            time.sleep(BATCH_PAUSE)

    client.close()
    print(f"\n{'='*50}")
    print(f"采集完成: 成功 {success}, 失败 {failed}, 总计 {count}")
    print(f"输出目录: {output_dir}")
    print(f"{'='*50}")


if __name__ == "__main__":
    main()
