#!/usr/bin/env python3
"""Deterministically validate the D-02 Hal Finney evidence map."""

from __future__ import annotations

import csv
import hashlib
import json
import re
from collections import Counter
from pathlib import Path
from urllib.parse import urlparse


CSV_PATH = Path(__file__).with_name("hal-finney-evidence-map.csv")
ARTICLE_PATH = Path(__file__).with_name("index.md")
REQUIRED_COLUMNS = (
    "record_id",
    "claim_id",
    "article_section",
    "event_date",
    "artifact",
    "role",
    "evidence_type",
    "what_it_proves",
    "what_it_cannot_prove",
    "primary_url",
    "archive_url",
    "checked_utc",
)
ALLOWED_ROLES = {
    "encrypted communication",
    "anonymous communication",
    "private payments",
    "protocol analysis",
    "political boundary",
    "reusable work",
    "trust boundary",
    "Bitcoin review",
    "Bitcoin testing",
    "Bitcoin transfer",
    "firsthand retrospective",
}
ALLOWED_EVIDENCE_TYPES = {
    "contemporaneous document",
    "contemporaneous post",
    "contemporaneous article",
    "contemporaneous announcement",
    "project documentation",
    "on-chain record",
    "retrospective self-report",
}
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
UTC_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
ARCHIVE_RE = re.compile(r"^https://web\.archive\.org/web/(\d{14})/(https?://.+)$")


def is_https(value: str) -> bool:
    parsed = urlparse(value)
    return parsed.scheme == "https" and bool(parsed.netloc)


def main() -> int:
    raw = CSV_PATH.read_bytes()
    article = ARTICLE_PATH.read_text(encoding="utf-8")
    with CSV_PATH.open(encoding="utf-8", newline="") as handle:
        reader = csv.DictReader(handle)
        rows = list(reader)
        fieldnames = tuple(reader.fieldnames or ())

    errors: list[str] = []
    if fieldnames != REQUIRED_COLUMNS:
        errors.append(f"columns must be exactly {REQUIRED_COLUMNS}")
    if len(rows) != 12:
        errors.append(f"expected 12 evidence records; found {len(rows)}")

    ids: set[str] = set()
    claim_ids: set[str] = set()
    primary_urls: set[str] = set()
    for line_number, row in enumerate(rows, start=2):
        missing = [name for name in REQUIRED_COLUMNS if not row.get(name, "").strip()]
        if missing:
            errors.append(f"line {line_number}: empty fields: {', '.join(missing)}")
        record_id = row.get("record_id", "")
        if record_id in ids:
            errors.append(f"line {line_number}: duplicate record_id {record_id}")
        ids.add(record_id)
        claim_id = row.get("claim_id", "")
        if claim_id in claim_ids:
            errors.append(f"line {line_number}: duplicate claim_id {claim_id}")
        claim_ids.add(claim_id)
        expected_id = f"HF-{line_number - 1:02d}"
        if record_id != expected_id:
            errors.append(f"line {line_number}: expected record_id {expected_id}")
        section = row.get("article_section", "")
        if f"## {section}" not in article:
            errors.append(f"line {line_number}: article section not found: {section}")
        if not DATE_RE.fullmatch(row.get("event_date", "")):
            errors.append(f"line {line_number}: event_date must be YYYY-MM-DD")
        if row.get("role") not in ALLOWED_ROLES:
            errors.append(f"line {line_number}: unknown role {row.get('role')!r}")
        if row.get("evidence_type") not in ALLOWED_EVIDENCE_TYPES:
            errors.append(f"line {line_number}: unknown evidence_type {row.get('evidence_type')!r}")
        primary_url = row.get("primary_url", "")
        if primary_url in primary_urls:
            errors.append(f"line {line_number}: duplicate primary_url {primary_url}")
        primary_urls.add(primary_url)
        if not is_https(primary_url):
            errors.append(f"line {line_number}: primary_url must be HTTPS")
        archive_url = row.get("archive_url", "")
        archive_match = ARCHIVE_RE.fullmatch(archive_url)
        if not archive_match:
            errors.append(f"line {line_number}: archive_url must be an exact 14-digit Wayback replay")
        elif urlparse(archive_match.group(2)).netloc.lower() != urlparse(primary_url).netloc.lower():
            errors.append(f"line {line_number}: archive target host differs from primary_url")
        if not UTC_RE.fullmatch(row.get("checked_utc", "")):
            errors.append(f"line {line_number}: checked_utc must be UTC ISO-8601")

    report = {
        "file": CSV_PATH.name,
        "sha256": hashlib.sha256(raw).hexdigest(),
        "records": len(rows),
        "claims": len(claim_ids),
        "roles": dict(sorted(Counter(row.get("role", "") for row in rows).items())),
        "errors": errors,
        "status": "PASS" if not errors else "FAIL",
    }
    print(json.dumps(report, indent=2, sort_keys=True))
    return 0 if not errors else 1


if __name__ == "__main__":
    raise SystemExit(main())
