#!/usr/bin/env bash
# 需連網執行：curl 抓取每筆 source_url 的回應本文，再用 shasum -a 256 比對。
# 本次環境離線；連網抓取尚未實測。預設只顯示用法，加 --fetch 才下載。
# 比對的是下載檔案的原始位元組，不是網址字串、畫面截圖或擷取後的文章文字。
# 原始擷取方式未載於帳本；網頁變動、動態內容、編碼或擷取差異都可能造成不相符。
set -euo pipefail

usage() {
    printf '%s\n' \
        '用法：bash verify_anchors.sh --fetch [anchors.json 路徑]' \
        '需連網執行；省略帳本路徑時使用專案 dist/ida-tk-lin/anchors.json。' \
        '下載檔、HTTP 資訊及對帳表留在腳本旁新建的 anchor-check.* 資料夾。' \
        'exit 0：全部相符；exit 1：有不相符／下載或計算失敗；exit 2：輸入或環境錯誤。'
}

if [[ $# -eq 0 || ${1:-} == --help ]]; then
    usage
    exit 0
fi
if [[ $1 != --fetch || $# -gt 2 ]]; then
    usage >&2
    exit 2
fi
shift

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
LEDGER=${1:-"$SCRIPT_DIR/../../dist/ida-tk-lin/anchors.json"}
for dependency in python3 curl shasum mktemp; do
    if ! command -v "$dependency" >/dev/null 2>&1; then
        printf '缺少必要工具：%s\n' "$dependency" >&2
        exit 2
    fi
done

# 先完整解析與檢查；解析失敗不得當作零筆成功，也不發出網路請求。
if ! ROWS=$(python3 - "$LEDGER" <<'PY'
import json
import re
import sys
from pathlib import Path
from urllib.parse import urlsplit

try:
    data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
    if not isinstance(data, dict) or not isinstance(data.get("record"), list) or not data["record"]:
        raise ValueError("帳本必須含有非空的 record 陣列")
    rows = []
    for index, record in enumerate(data["record"], 1):
        if not isinstance(record, dict):
            raise ValueError(f"第 {index} 筆不是 JSON 物件")
        source = record.get("source_url")
        digest = record.get("sha256")
        if not isinstance(source, str) or any(c.isspace() or ord(c) < 32 or ord(c) == 127 for c in source):
            raise ValueError(f"第 {index} 筆 source_url 缺漏或含空白／控制字元")
        parts = urlsplit(source)
        if parts.scheme not in ("http", "https") or not parts.hostname:
            raise ValueError(f"第 {index} 筆 source_url 必須為完整 HTTP(S) URL")
        if not isinstance(digest, str) or re.fullmatch(r"[0-9a-fA-F]{64}", digest) is None:
            raise ValueError(f"第 {index} 筆 sha256 不是 64 位十六進位")
        rows.append(f"{index}\t{source}\t{digest.lower()}")
    print("\n".join(rows))
except (OSError, UnicodeError, ValueError) as exc:
    print(f"帳本解析失敗：{exc}", file=sys.stderr)
    sys.exit(2)
PY
); then
    exit 2
fi

OUTPUT_DIR=$(mktemp -d "$SCRIPT_DIR/anchor-check.XXXXXX")
REPORT="$OUTPUT_DIR/results.tsv"
printf '序號\t來源URL\t帳本sha256\t本次sha256\t結果\n' > "$REPORT"
printf '下載與對帳留底：%s\n' "$OUTPUT_DIR"
total=0
matched=0
mismatched=0
failed=0
while IFS=$'\t' read -r index source expected; do
    total=$((total + 1))
    body="$OUTPUT_DIR/$index.body"
    actual='未計算'
    printf '\n#%s %s\n' "$index" "$source"
    # 需連網執行：跟隨重新導向，HTTP 錯誤不當成正常頁面計算指紋。
    # -q 禁用本機 .curlrc；不做文字正規化、不自動解壓縮、不加入 HTTP 標頭到本文。
    if curl -q --fail --silent --show-error --location \
        --proto '=http,https' --proto-redir '=http,https' \
        --connect-timeout 15 --max-time 90 \
        --dump-header "$OUTPUT_DIR/$index.headers" \
        --output "$body" \
        --write-out 'http_code=%{http_code}\neffective_url=%{url_effective}\n' \
        --url "$source" > "$OUTPUT_DIR/$index.http"; then
        if hash_line=$(shasum -a 256 "$body"); then
            actual=${hash_line%% *}
            if [[ $actual == "$expected" ]]; then
                result='相符'
                matched=$((matched + 1))
            else
                result='不相符（需檢查版本與擷取方式）'
                mismatched=$((mismatched + 1))
            fi
        else
            result='計算失敗'
            failed=$((failed + 1))
        fi
    else
        curl_status=$?
        result="下載失敗（curl exit ${curl_status}；未計算）"
        failed=$((failed + 1))
    fi
    printf '帳本：%s\n重算：%s\n結果：%s\n' "$expected" "$actual" "$result"
    printf '%s\t%s\t%s\t%s\t%s\n' "$index" "$source" "$expected" "$actual" "$result" >> "$REPORT"
done <<< "$ROWS"

printf '\n總筆數：%s；相符：%s；不相符：%s；失敗：%s\n' "$total" "$matched" "$mismatched" "$failed"
printf '對帳表：%s\n' "$REPORT"
printf '%s\n' '指紋相符只支持位元組一致，不代表來源內容或文字主張已被證實。'
if [[ $mismatched -gt 0 || $failed -gt 0 ]]; then
    exit 1
fi
