#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""check-updates.py — 来源轻量更新检查器（简报第 19 节 UPDATE SYSTEM）

用法（仓库根目录）:
    python3 scripts/check-updates.py               # 检查 sources/sources.csv 全部 URL
    python3 scripts/check-updates.py --limit 10    # 只检查前 10 条
    python3 scripts/check-updates.py --timeout 15  # 自定义超时秒数（默认 10）

行为:
    - 逐条读取 sources/sources.csv 的 url 列（按行序）
    - 先发 HEAD；若服务器拒绝 HEAD（405/501 等）自动退回 GET（不落盘正文）
    - 记录 HTTP 状态、最终 URL（跟随重定向后）、ETag / Last-Modified（有则记录）
    - 输出 sources/update-report.txt，并在其中标出需人工复查的条目:
        * csv 中 status ≠ ACTIVE
        * 发生了重定向（最终 URL ≠ 原始 URL）
        * 请求失败（DNS/超时/SSL 等）
    - 纯 Python 标准库；不发送任何密钥；User-Agent 标明来源用途
"""

import argparse
import csv
import datetime
import pathlib
import sys
import urllib.error
import urllib.request

ROOT = pathlib.Path(__file__).resolve().parent.parent
SOURCES_CSV = ROOT / "sources" / "sources.csv"
REPORT_TXT = ROOT / "sources" / "update-report.txt"
USER_AGENT = "claude-architect-study-update-checker/1.0 (lightweight source freshness check)"


def probe(url: str, timeout: float):
    """对单个 URL 做 HEAD（必要时退回 GET）。返回 dict 结果。"""
    result = {"status": None, "final_url": None, "etag": None, "last_modified": None,
              "redirected": False, "error": None, "method": "HEAD"}
    for method in ("HEAD", "GET"):
        result["method"] = method
        req = urllib.request.Request(url, method=method,
                                     headers={"User-Agent": USER_AGENT})
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                result["status"] = resp.status
                result["final_url"] = resp.geturl()
                result["etag"] = resp.headers.get("ETag")
                result["last_modified"] = resp.headers.get("Last-Modified")
                break  # 成功即止；GET 仅在 HEAD 被拒后才尝试
        except urllib.error.HTTPError as e:
            result["status"] = e.code
            result["final_url"] = getattr(e, "url", None) or url
            result["etag"] = e.headers.get("ETag") if e.headers else None
            result["last_modified"] = e.headers.get("Last-Modified") if e.headers else None
            if method == "HEAD" and e.code in (405, 501, 400, 403, 418):
                continue  # 服务器可能不支持 HEAD，退回 GET 重试
            if e.code in (301, 302, 303, 307, 308):
                # urllib 通常自动跟随重定向；走到这里说明未跟随（少见）
                result["error"] = f"HTTP {e.code} not followed; Location: {e.headers.get('Location')}"
            else:
                result["error"] = f"HTTP {e.code} {e.reason}"
            break
        except Exception as e:  # DNS / 超时 / SSL 等
            result["error"] = f"{type(e).__name__}: {e}"
            break
    if result["final_url"] and result["final_url"] != url:
        result["redirected"] = True
    return result


def main():
    ap = argparse.ArgumentParser(description="检查 sources.csv 中来源 URL 的可达性与变化")
    ap.add_argument("--limit", type=int, default=0, help="只检查前 N 条（0=全部）")
    ap.add_argument("--timeout", type=float, default=10.0, help="每请求超时秒数（默认 10）")
    args = ap.parse_args()

    if not SOURCES_CSV.exists():
        sys.exit(f"未找到 {SOURCES_CSV}")

    with open(SOURCES_CSV, newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))
    if args.limit > 0:
        rows = rows[: args.limit]

    lines = []
    flagged = []
    ran_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
    lines.append(f"# 来源更新检查报告 — 生成于 {ran_at}")
    lines.append(f"# 命令参数: --limit {args.limit if args.limit else 'ALL'} --timeout {args.timeout}")
    lines.append(f"# 检查条数: {len(rows)} ｜ 判定规则: HTTP 状态 + 重定向 + ETag/Last-Modified 快照")
    lines.append("")

    for r in rows:
        sid, url, status_csv = r["id"], r["url"], r["status"]
        res = probe(url, args.timeout)
        reasons = []
        if status_csv != "ACTIVE":
            reasons.append(f"csv_status={status_csv}")
        if res["error"]:
            reasons.append(f"fetch_error({res['error']})")
        elif res["status"] and res["status"] >= 400:
            reasons.append(f"http_{res['status']}")
        if res["redirected"]:
            reasons.append("redirect")
        tag = " ⚑REVIEW" if reasons else ""
        lines.append(
            f"[{sid}] {res['status'] if res['status'] else 'ERR'} via {res['method']}{tag}\n"
            f"    url:      {url}\n"
            f"    final:    {res['final_url'] or '-'}\n"
            f"    etag:     {res['etag'] or '-'} ｜ last-modified: {res['last_modified'] or '-'}\n"
            f"    csv:      status={status_csv} importance={r['importance']}"
        )
        if reasons:
            flagged.append((sid, "; ".join(reasons)))
        print(f"[{sid}] {'OK' if not reasons else 'FLAG'} {res['status'] or res['error']}")

    lines.append("")
    lines.append(f"## 需人工复查（{len(flagged)} 条）")
    if flagged:
        for sid, why in flagged:
            lines.append(f"- {sid}: {why}")
    else:
        lines.append("- 无")

    REPORT_TXT.parent.mkdir(parents=True, exist_ok=True)
    REPORT_TXT.write_text("\n".join(lines) + "\n", encoding="utf-8")
    print(f"\n报告已写入: {REPORT_TXT.relative_to(ROOT)} ｜ 复查标记: {len(flagged)}/{len(rows)}")


if __name__ == "__main__":
    main()
