#!/usr/bin/env python3
"""
衢州市生态环境局 - 行政许可 爬虫
www.qz.gov.cn/col/col1229709391/index.html
列表: JPAAS unitbuild API (pageId=1229709391, tagId=当前栏目的ajax分页, paramJson 分页)
详情: requests 直抓, #zoom 正文保留 <p>/<table> HTML
"""
import os, sys, re, time, json
import requests
from urllib.parse import urljoin

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from crawler_lib import push_to_searchdb, normalize_pub_date

BASE_URL = "https://www.qz.gov.cn"
COL_ID = "1229709391"
SITE_NAME = "衢州市生态环境局-行政许可"
API_URL = "https://www.qz.gov.cn/api-gateway/jpaas-publish-server/front/page/build/unit"
API_PARAMS = {
    "parseType": "bulidstatic", "webId": "3084",
    "tplSetId": "i58lqHVn2cokOgipAEjQF", "pageType": "column",
    "tagId": "当前栏目的ajax分页", "editType": "null",
    "pageId": COL_ID,
}
PAGE_SIZE = 20
CUTOFF = "2023-08-10"  # 3 年窗口

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "X-Requested-With": "XMLHttpRequest",
    "Referer": f"{BASE_URL}/col/col{COL_ID}/index.html",
}

def parse_pages_arg():
    """支持 --pages=N 和纯数字"""
    for a in sys.argv[1:]:
        if a.startswith("--pages="):
            return int(a.split("=")[1])
        if a.isdigit():
            return int(a)
    return None

def fetch_list_page(page_num):
    """通过 unitbuild API 获取列表页 HTML"""
    params = dict(API_PARAMS)
    params["paramJson"] = json.dumps({"pageNo": page_num, "pageSize": PAGE_SIZE}, ensure_ascii=False)
    for attempt in range(3):
        try:
            r = requests.get(API_URL, params=params, headers=HEADERS, timeout=25)
            if r.status_code != 200:
                print(f"  [WARN] API page {page_num} HTTP {r.status_code}, retry")
                time.sleep(2)
                continue
            d = r.json()
            html = d.get("data", {}).get("html", "") if isinstance(d, dict) else ""
            return html
        except Exception as e:
            print(f"  [WARN] API page {page_num} error: {e}, retry")
            time.sleep(2)
    return ""

def parse_list_items(html):
    """从列表 HTML 提取 (title, url, date)"""
    items = []
    # li 块解析, 兼容 a[title] + span.bt-data-time
    for m in re.finditer(r'<li[^>]*class="[^"]*"[^>]*>(.*?)</li>', html, re.DOTALL):
        block = m.group(1)
        am = re.search(r'<a[^>]*href="([^"]+)"[^>]*title="([^"]*)"', block)
        if not am:
            am = re.search(r'<a[^>]*href="([^"]+)"[^>]*>([^<]{5,})</a>', block)
        if not am:
            continue
        url, title = am.group(1), am.group(2).strip()
        dm = re.search(r'(\d{4}-\d{2}-\d{2})', block)
        date = dm.group(1) if dm else ""
        # 过滤分页导航 li (首页/上页/页码/下页/尾页/刷新)
        if title in ("首页", "上页", "页码", "下页", "尾页", "刷新") or not title:
            continue
        if not url.startswith("http"):
            url = urljoin(BASE_URL + f"/col/col{COL_ID}/", url)
        items.append({"title": title, "url": url, "date": date})
    return items

def clean_title(title):
    """清洗标题: 剥实体/零宽/装饰符"""
    import html as html_mod
    title = html_mod.unescape(title)
    title = re.sub(r"&middot;|&nbsp;", "", title)
    title = re.sub(r"[\u200b\ufeff]", "", title)
    title = re.sub(r"^[•·]\s*", "", title)
    return title.strip()

def extract_detail(page_url):
    """提取标题/日期/正文(保留 <p>/<table> HTML)/附件"""
    for attempt in range(3):
        try:
            r = requests.get(page_url, headers=HEADERS, timeout=25)
            r.encoding = "utf-8"
            if r.status_code != 200:
                print(f"  [WARN] detail HTTP {r.status_code} retry")
                time.sleep(2)
                continue
            html = r.text
            break
        except Exception as e:
            print(f"  [WARN] detail error: {e} retry")
            time.sleep(2)
    else:
        return None, None, None, None

    # 标题: h2 / meta ArticleTitle / title
    title = ""
    m = re.search(r'<h2[^>]*>(.*?)</h2>', html, re.DOTALL)
    if m:
        title = clean_title(re.sub(r"<[^>]+>", "", m.group(1)))
    if not title:
        m = re.search(r'<meta[^>]*name=["\']ArticleTitle["\'][^>]*content=["\']([^"\']*)["\']', html, re.I)
        if m:
            title = clean_title(m.group(1))
    if not title:
        m = re.search(r'<title>(.*?)</title>', html, re.S)
        if m:
            title = clean_title(m.group(1))

    # 日期: meta PubDate / 发布日期文本
    date = ""
    m = re.search(r'<meta[^>]*name=["\']PubDate["\'][^>]*content=["\'](\d{4}-\d{2}-\d{2})', html, re.I)
    if m:
        date = m.group(1)
    if not date:
        for kw in ["发布日期", "成文日期", "发布时间"]:
            m = re.search(r'%s[：:]\s*(\d{4}-\d{2}-\d{2})' % kw, html)
            if m:
                date = m.group(1)
                break

    # 正文: #zoom 平衡匹配
    content = ""
    attachments = []
    zoom_html = None
    # 用 BS 更稳
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, "html.parser")
    zoom = soup.find(id="zoom")
    if zoom is None:
        zoom = soup.find(class_="zoom")
    if zoom:
        # 附件: a 标签指向下载/文档
        for a in zoom.find_all("a", href=True):
            href = a["href"].strip()
            if re.search(r'\.(pdf|doc|docx|xls|xlsx|zip|rar|wps)$', href.lower()) or "download" in href.lower():
                full = urljoin(page_url, href)
                name = a.get_text(strip=True) or href.split("/")[-1]
                attachments.append({"name": name, "url": full})
        # 正文: 保留 <p>/<table>/<img>, 剥内联样式规范化 <p>
        body_parts = []
        for el in zoom.find_all(["p", "table", "img"]):
            if el.name == "p":
                if el.find_parent("table") or el.find("table") or el.find("p"):
                    continue
                txt = el.get_text(" ", strip=True)
                txt = re.sub(r"\s+", " ", txt)
                if txt:
                    body_parts.append(f"<p>{txt}</p>")
            elif el.name == "table":
                body_parts.append(str(el))
            elif el.name == "img":
                src = el.get("src", "")
                if src:
                    full = urljoin(page_url, src)
                    alt = el.get("alt", "")
                    body_parts.append(f'<p><img src="{full}" alt="{alt}"></p>')
        # 附件段落
        for att in attachments:
            body_parts.append(f'<p><a href="{att["url"]}">{att["name"]}</a></p>')
        content = "\n".join(body_parts)

    # 兜底: 无 #zoom 时用 sx_Textcontent 或纯文本
    if not content:
        other = soup.find(class_="sx_Textcontent")
        if other:
            ps = []
            for el in other.find_all(["p", "table", "img"]):
                if el.name == "p" and not el.find_parent("table") and not el.find("table") and not el.find("p"):
                    txt = el.get_text(" ", strip=True)
                    if txt:
                        ps.append(f"<p>{txt}</p>")
                elif el.name == "table":
                    ps.append(str(el))
                elif el.name == "img" and el.get("src"):
                    ps.append(f'<p><img src="{urljoin(page_url, el["src"])}"></p>')
            content = "\n".join(ps)
        if not content:
            txt = soup.get_text(" ", strip=True)
            content = f"<p>{txt}</p>" if txt else ""

    return title, date, content, attachments

def main():
    max_pages = parse_pages_arg()
    if max_pages is None:
        max_pages = 60  # 默认全量(219条/20=11页)

    print(f"[AutoPg] max_pages={max_pages} (SITE={SITE_NAME})")
    items = []
    seen_urls = set()
    for pg in range(1, max_pages + 1):
        html = fetch_list_page(pg)
        if not html or len(html) < 50:
            print(f"  Page {pg}: 空页, 停止")
            break
        page_items = parse_list_items(html)
        new_items = [x for x in page_items if x["url"] not in seen_urls]
        for x in new_items:
            seen_urls.add(x["url"])
        items.extend(new_items)
        print(f"  Page {pg}: {len(page_items)}条(新{len(new_items)}) 累计{len(items)}")
        # CUTOFF 检查: 列表日期 < CUTOFF 即停
        if new_items:
            dates = [x["date"] for x in new_items if x["date"]]
            if dates and min(dates) < CUTOFF:
                print(f"  已达 CUTOFF {CUTOFF}, 停止翻页")
                break
        time.sleep(0.5)

    print(f"\n共 {len(items)} 条列表项, 开始抓详情...")
    results = []
    for i, item in enumerate(items):
        title, date, content, attachments = extract_detail(item["url"])
        if not title:
            title = item["title"]
        if not date:
            date = item["date"]
        if not content:
            print(f"  [WARN] 空正文: {title[:50]}")

        results.append({
            "site_name": SITE_NAME,
            "title": title,
            "pub_date": date,
            "content": content,
            "source_url": item["url"],
            "url": item["url"],
            "category": "行政许可",
            "attachments": json.dumps(attachments, ensure_ascii=False) if attachments else "",
        })
        if (i + 1) % 20 == 0:
            print(f"  [{i+1}/{len(items)}] 最新: {title[:40]}")
        time.sleep(0.3)

    push_to_searchdb(results, batch_label=SITE_NAME)
    print(f"\n{'='*50}")
    print(f"站点: {SITE_NAME}")
    print(f"采集条数: {len(results)}")
    print(f"日期范围: {min((r['pub_date'] for r in results if r['pub_date']), default='-')} ~ {max((r['pub_date'] for r in results if r['pub_date']), default='-')}")
    print(f"{'='*50}")

if __name__ == "__main__":
    main()
