import argparse
import csv
import datetime as dt
import html
import json
import pathlib
import re
import time
import urllib.parse
import urllib.request
from collections import Counter


BASE = "https://www.xinje.com"
DOWNLOAD_ENDPOINT = "/web/downloadCenter/file"

DOCUMENT_CATEGORIES = {
    16: "产品手册",
    19: "彩页/样本",
    31: "产品图纸",
    33: "EPLAN",
    49: "认证证书",
}

PLC_HMI_KEYWORDS = [
    "PLC",
    "可编程",
    "控制器",
    "XDP",
    "XDPPro",
    "TouchWin",
    "人机界面",
    "HMI",
    "XD",
    "XL",
    "XG",
    "XC",
    "XS",
    "TS",
    "TG",
]


def fetch(params: dict[str, str | int]) -> str:
    query = urllib.parse.urlencode(params)
    url = f"{BASE}{DOWNLOAD_ENDPOINT}?{query}"
    request = urllib.request.Request(
        url,
        headers={
            "User-Agent": "Mozilla/5.0 (Codex Xinje catalog)",
            "X-Requested-With": "XMLHttpRequest",
        },
    )
    with urllib.request.urlopen(request, timeout=40) as response:
        return response.read().decode("utf-8", "ignore")


def clean_text(value: str) -> str:
    value = re.sub(r"<[^>]+>", "", value)
    return html.unescape(re.sub(r"\s+", " ", value).strip())


def text_between(pattern: str, block: str) -> str:
    match = re.search(pattern, block, flags=re.I | re.S)
    return clean_text(match.group(1)) if match else ""


def parse_results(markup: str) -> list[dict[str, str]]:
    blocks = re.findall(r"(<li>\s*<div class=\"con\">.*?</li>)", markup, flags=re.I | re.S)
    results = []
    for block in blocks:
        href_match = re.search(r'href=["\']([^"\']+)["\']', block, flags=re.I)
        href = html.unescape(href_match.group(1)) if href_match else ""
        title = text_between(r"<h3[^>]*>(.*?)</h3>", block)
        download_name = text_between(r'download=["\']([^"\']+)["\']', block)
        results.append(
            {
                "id": text_between(r'id=["\']file-(\d+)["\']', block),
                "title": title or download_name,
                "download_name": download_name,
                "url": href,
                "size": text_between(r'class=["\']filesize["\'][^>]*>.*?<em>(.*?)</em>', block),
                "version": text_between(r'class=["\']filever["\'][^>]*>.*?<em>(.*?)</em>', block),
                "file_type": text_between(r'class=["\']filetype["\'][^>]*>.*?<em>(.*?)</em>', block),
                "update_date": text_between(r'class=["\']filedate["\'][^>]*>.*?<em>(.*?)</em>', block),
            }
        )
    return results


def parse_max_page(markup: str, one_id: int) -> int:
    pages = [1]
    pattern = rf"changePage\((\d+),\s*0,\s*{one_id},\s*0\)"
    pages.extend(int(value) for value in re.findall(pattern, markup))
    jump_match = re.search(r'id=["\']pageJump["\'][^>]+max=["\'](\d+)["\']', markup, flags=re.I)
    if jump_match:
        pages.append(int(jump_match.group(1)))
    return max(pages)


def collect_category(one_id: int, delay: float) -> list[dict[str, str]]:
    first_markup = fetch({"seriesId": 0, "oneId": one_id, "twoId": 0, "page": 1, "fileName": ""})
    max_page = parse_max_page(first_markup, one_id)
    items = []
    for page in range(1, max_page + 1):
        markup = first_markup if page == 1 else fetch(
            {"seriesId": 0, "oneId": one_id, "twoId": 0, "page": page, "fileName": ""}
        )
        for item in parse_results(markup):
            item["category_id"] = str(one_id)
            item["category"] = DOCUMENT_CATEGORIES.get(one_id, str(one_id))
            item["source"] = f"{BASE}/web/downloadCenter/index"
            items.append(item)
        if delay:
            time.sleep(delay)
    return items


def write_csv(path: pathlib.Path, rows: list[dict[str, str]]) -> None:
    fieldnames = [
        "id",
        "category_id",
        "category",
        "title",
        "download_name",
        "file_type",
        "size",
        "version",
        "update_date",
        "url",
        "source",
    ]
    with path.open("w", encoding="utf-8-sig", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        for row in rows:
            writer.writerow({field: row.get(field, "") for field in fieldnames})


def write_json(path: pathlib.Path, rows: list[dict[str, str]]) -> None:
    payload = {
        "cataloged_at": dt.datetime.now(dt.timezone.utc).isoformat(),
        "source": f"{BASE}/web/downloadCenter/index",
        "scope": "Xinje download-center document-like categories; metadata only, no file downloads",
        "categories": DOCUMENT_CATEGORIES,
        "total_items": len(rows),
        "items": rows,
    }
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")


def write_markdown(path: pathlib.Path, rows: list[dict[str, str]]) -> None:
    by_category = Counter(row["category"] for row in rows)
    by_type = Counter((row.get("file_type") or "unknown").lower() for row in rows)
    plc_hmi = [
        row for row in rows
        if any(keyword.lower() in (row.get("title", "") + row.get("download_name", "")).lower()
               for keyword in PLC_HMI_KEYWORDS)
    ]
    recent = sorted(rows, key=lambda row: row.get("update_date", ""), reverse=True)[:20]

    lines = [
        "# Xinje Official Document Catalog",
        "",
        f"Cataloged at: {dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
        "",
        "Scope: metadata from Xinje download-center document-like categories. Files are not downloaded by this script.",
        "",
        "## Counts By Category",
        "",
        "| Category | Count |",
        "|---|---:|",
    ]
    for category, count in by_category.most_common():
        lines.append(f"| {category} | {count} |")

    lines.extend(["", "## Counts By File Type", "", "| File type | Count |", "|---|---:|"])
    for file_type, count in by_type.most_common():
        lines.append(f"| {file_type} | {count} |")

    lines.extend([
        "",
        "## Recent Items",
        "",
        "| ID | Category | Title | Type | Version | Update date |",
        "|---|---|---|---|---|---|",
    ])
    for row in recent:
        lines.append(
            f"| {row.get('id','')} | {row.get('category','')} | {row.get('title','')} | "
            f"{row.get('file_type','')} | {row.get('version','')} | {row.get('update_date','')} |"
        )

    lines.extend([
        "",
        "## PLC/HMI Candidates",
        "",
        "Use the full CSV/JSON catalog for exact URLs. This section highlights likely PLC/HMI-related entries.",
        "",
        "| ID | Category | Title | Type | Version | Update date |",
        "|---|---|---|---|---|---|",
    ])
    for row in plc_hmi[:120]:
        lines.append(
            f"| {row.get('id','')} | {row.get('category','')} | {row.get('title','')} | "
            f"{row.get('file_type','')} | {row.get('version','')} | {row.get('update_date','')} |"
        )

    path.write_text("\n".join(lines) + "\n", encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser(description="Catalog Xinje download-center document metadata.")
    parser.add_argument("--output-dir", default="assets/catalogs", help="Directory for JSON/CSV/MD outputs.")
    parser.add_argument("--delay", type=float, default=0.15, help="Delay between page requests.")
    parser.add_argument("--categories", default=",".join(str(key) for key in DOCUMENT_CATEGORIES))
    args = parser.parse_args()

    rows = []
    seen = set()
    for one_id in [int(value) for value in args.categories.split(",") if value.strip()]:
        for item in collect_category(one_id, args.delay):
            key = item.get("id") or item.get("url")
            if key in seen:
                continue
            seen.add(key)
            rows.append(item)

    rows.sort(key=lambda row: (row.get("category_id", ""), row.get("update_date", ""), row.get("id", "")), reverse=True)

    output_dir = pathlib.Path(args.output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    write_json(output_dir / "xinje-official-document-catalog.json", rows)
    write_csv(output_dir / "xinje-official-document-catalog.csv", rows)
    write_markdown(output_dir / "xinje-official-document-catalog-summary.md", rows)
    print(f"cataloged {len(rows)} items into {output_dir}")


if __name__ == "__main__":
    main()
