#!/usr/bin/env python3
"""
咸宁市-环评审批 (sthjj.xianning.gov.cn)
==============================
CMS: 咸宁政府门户 (TRS 内容管理)
列表: /xxgk/fdzdgknr/gysyjs/sthj_30351/hpsp/
详情: /.../202602/t20260213_4264790.shtml
正文: div.view.TRS_UEDITOR

用法:
    python3 crawl_xianning.py          # 全量
    python3 crawl_xianning.py --test   # 测试 5 条
"""

import re, sys, os, time
from datetime import datetime, timezone, timedelta
import requests, urllib3
import os
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

SITE_NAME  = "咸宁市-环评审批"
BASE_URL   = "http://sthjj.xianning.gov.cn"
LIST_URL   = "http://sthjj.xianning.gov.cn/xxgk/fdzdgknr/gysyjs/sthj_30351/hpsp/"
SEARCH_DB = os.getenv("SEARCH_DB", "/root/search.db")
CUTOFF     = (datetime.now(timezone.utc) - timedelta(days=365*3)).strftime("%Y-%m-%d")
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"}

def fetch(url):
    try:
        r = requests.get(url, headers=HEADERS, timeout=20, verify=False)
        r.encoding = 'utf-8'
        return r.text
    except Exception as e:
        print("  ! fetch fail: " + str(e))
        return None

def parse_list(html):
    items = []
    for m in re.finditer(r'<li[^>]*>(.*?)</li>', html, re.DOTALL):
        li = m.group(1)
        link = re.search(r'href="([^"]+)"[^>]*title="([^"]*)"', li)
        if not link:
            link = re.search(r'href="([^"]+)"[^>]*>([^<]+)', li)
        date = re.search(r'<span>(\d{4}-\d{2}-\d{2})</span>', li)
        if link:
            href = link.group(1).strip()
            title = link.group(2).strip() if len(link.groups()) >= 2 else ""
            if title and len(title) > 5:
                items.append({
                    "title": title,
                    "url": href if href.startswith('http') else BASE_URL + href,
                    "pub_date": date.group(1) if date else ""
                })
    return items

def fetch_detail(url):
    html = fetch(url)
    if not html:
        return {"title": "", "content": "", "pub_date": ""}

    title = ""
    m = re.search(r'<title>([^<]+)', html)
    if m:
        # Remove trailing site name after dash/pipe
        title = re.split(r'[_\u2014\-|]', m.group(1))[0].strip()

    pub_date = ""
    m = re.search(r'(\d{4})\u5e74(\d{1,2})\u6708(\d{1,2})\u65e5', html)
    if m:
        pub_date = f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
    if not pub_date:
        m = re.search(r'(\d{4}-\d{2}-\d{2})', html)
        if m:
            pub_date = m.group(1)

    content = ""
    m = re.search(r'class="view TRS_UEDITOR[^"]*"[^>]*>(.*?)</div>', html, re.DOTALL)
    if m:
        content = m.group(1)
    if not content or len(content) < 50:
        m = re.search(r'class="content word_cen"[^>]*>(.*?)</div>', html, re.DOTALL)
        if m:
            content = m.group(1)

    if content:
        content = re.sub(r'<script[^>]*>.*?</script>', '', content, flags=re.DOTALL|re.I)
        content = re.sub(r'<style[^>]*>.*?</style>', '', content, flags=re.DOTALL|re.I)
        content = content.strip()

    return {"title": title, "content": content, "pub_date": pub_date}

def to_db(items):
    if not items:
        print("  - no data")
        return 0, 0
    import sqlite3
    db = sqlite3.connect(SEARCH_DB, timeout=60)
    db.execute("PRAGMA journal_mode=WAL")
    db.execute("PRAGMA synchronous=NORMAL")
    ok, fail = 0, 0
    for it in items:
        try:
            db.execute(
                "INSERT OR IGNORE INTO gov_raw "
                "(site_name, title, page_url, content, publish_date, summary, tags) "
                "VALUES (?,?,?,?,?,?,?)",
                (
                    SITE_NAME,
                    (it.get("title") or "")[:500],
                    it.get("url", ""),
                    it.get("content", ""),
                    (it.get("pub_date") or "")[:10],
                    "",
                    "环评审批",
                )
            )
            if db.total_changes > 0:
                ok += 1
            else:
                fail += 1
        except Exception as e:
            fail += 1
    # 2026-09-22: 先提交 gov_raw —— 库上触发器已维护 FTS，下面这条手动写入会因
    #   rowid 重复而 IntegrityError；不先 commit 会把 gov_raw 那条一并回滚（静默丢数据）
    db.commit()
    db.execute(
        "INSERT OR REPLACE INTO gov_search(rowid, title, site_name, summary) "
        "SELECT r.id, r.title, r.site_name, r.summary "
        "FROM gov_raw r WHERE r.id NOT IN (SELECT rowid FROM gov_search) AND r.site_name=?",
        (SITE_NAME,)
    )
    db.commit()
    db.close()
    print("  [DB] new: " + str(ok) + " skip: " + str(fail) + " FTS synced")
    return ok, fail

def crawl(test=False):
    html = fetch(LIST_URL)
    if not html:
        print("  FAILED to fetch list page")
        return 0, 0

    items = parse_list(html)
    if not items:
        print("  FAILED to parse list")
        return 0, 0

    all_items = [it for it in items if it["pub_date"] and it["pub_date"] >= CUTOFF]
    print("  List: " + str(len(items)) + " items, within 3yr: " + str(len(all_items)))
    if all_items:
        print("    Range: " + all_items[0]["pub_date"] + " ~ " + all_items[-1]["pub_date"])

    if test:
        all_items = all_items[:5]
        print("  TEST mode: " + str(len(all_items)) + " items")

    if not all_items:
        return 0, 0

    results = []
    for i, item in enumerate(all_items, 1):
        detail = fetch_detail(item["url"])
        entry = {
            "title": detail["title"] or item["title"],
            "url": item["url"],
            "content": detail["content"],
            "pub_date": detail["pub_date"] or item.get("pub_date", ""),
        }
        results.append(entry)
        print("  [" + str(i) + "/" + str(len(all_items)) + "] " + entry['pub_date'] + " " + entry['title'][:40] + "... (" + str(len(detail['content'])) + "B)")
        time.sleep(0.3)

    ok, fail = to_db(results)
    return ok, fail

if __name__ == "__main__":
    test = "--test" in sys.argv
    mode = "TEST" if test else "FULL"
    print()
    print("[" + SITE_NAME + "] " + mode)
    t0 = time.time()
    ok, fail = crawl(test=test)
    print("  Time: " + str(round(time.time()-t0, 1)) + "s")
    print("  New: " + str(ok) + " Skip: " + str(fail))
