#!/usr/bin/env python3
"""Check behind /blog/statement-table-across-pages.

1. Writes one synthetic two-page bank statement as ten PDFs with make_pdfs.py, through
   `uv run --with pikepdf==10.17.0`. All ten hold the same six transactions, the same opening and
   closing balance and the same period; they differ only in what the page break does to the table.
2. Reads each the way Evidilo's upload and conversion do (pagesOf, readStatementPeriod,
   readStatement with no model adapter, the rules lane a page with a text layer takes) through
   read-pages.harness.ts and vitest, with harness.vitest.config.mjs, and compares every row with the
   six transactions as that PDF printed them,
   a description that wraps onto a second line read in full.
3. Re-reads Microsoft's Pdf.Tables reference and its PDF connector page and confirms the quoted
   sentences are still there.

Exit 0: every result matches dataset.csv and the quote is found. Exit 1: a result differs or the
quote is gone. Exit 2: the cited page could not be reached at all (nothing else failed).
`--recheck` runs the same steps (none takes over a minute). `--write` rewrites dataset.csv.
"""

import csv
import html
import json
import os
import re
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request
from pathlib import Path

HERE = Path(__file__).resolve().parent
REPO = HERE.parents[4]  # apps/web/public/blog/<slug> -> repository root
PIKEPDF = "pikepdf==10.17.0"
UA = (
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/130.0 Safari/537.36"
)
QUOTES = {
    "https://learn.microsoft.com/en-us/powerquery-m/pdf-tables": [
        "MultiPageTables : Controls whether similar tables on consecutive pages will be "
        "automatically combined into a single table. Default: true.",
    ],
    "https://learn.microsoft.com/en-us/power-query/connectors/pdf": [
        "Returns any tables found in a PDF file.",
    ],
}
FIELDS = [
    "id", "case", "rows_read", "rows_matching_print", "row_differences", "transfer_row_page",
    "closing_balance_read", "balance_check", "difference", "rows_marked_for_review", "lanes",
]


def measure():
    with tempfile.TemporaryDirectory() as tmp:
        pdfs = Path(tmp) / "pdfs"
        subprocess.run(
            ["uv", "run", "-q", "--with", PIKEPDF, "python3", str(HERE / "make_pdfs.py"), str(pdfs)],
            check=True, stdout=subprocess.DEVNULL,
        )
        out = Path(tmp) / "pages.json"
        env = dict(os.environ, PAGES_DIR=str(pdfs), PAGES_OUT=str(out))
        subprocess.run(
            ["npx", "vitest", "run", "--config", str(HERE / "harness.vitest.config.mjs")],
            cwd=REPO, env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
        )
        read = json.loads(out.read_text())["cases"]
        printed = json.loads((pdfs / "cases.json").read_text())

    labels = {c["id"]: c["label"] for c in printed["cases"]}
    rows = []
    for case in read:
        if case["outcome"] != "read":
            rows.append({**{f: "" for f in FIELDS}, "id": case["id"], "case": labels[case["id"]],
                         "rows_read": "refused: " + case.get("message", "")})
            continue
        got = case["rows"]
        truth = printed["printed"][case["id"]]
        differences = []
        matching = 0
        for i, want in enumerate(truth):
            have = got[i] if i < len(got) else None
            if have is None:
                differences.append(f'row {i + 1} missing')
                continue
            want_amount = want["amount"].replace(",", "")
            want_balance = want["balance"].replace(",", "")
            want_date = "2026-" + want["date"].replace("/", "-")
            diff = []
            if have["date"] != want_date:
                diff.append(f'date {have["date"]}')
            if have["amount"] != want_amount:
                diff.append(f'amount {have["amount"]}')
            if have["balance"] != want_balance:
                diff.append(f'balance {have["balance"]}')
            if have["description"] != want["description"]:
                diff.append(f'description "{have["description"]}"')
            if diff:
                differences.append(f'row {i + 1}: ' + ", ".join(diff))
            else:
                matching += 1
        continuity = case["balanceContinuity"] or {}
        difference = (continuity.get("difference") or {}).get("minorUnits")
        rows.append({
            "id": case["id"],
            "case": labels[case["id"]],
            "rows_read": str(len(got)),
            "rows_matching_print": str(matching),
            "row_differences": "; ".join(differences) or "none",
            "transfer_row_page": str(got[3]["page"]) if len(got) > 3 else "",
            "closing_balance_read": case["closing"] or "",
            "balance_check": continuity.get("status", "not run"),
            "difference": "" if difference is None else f"{int(difference) / 100:.2f}",
            "rows_marked_for_review": " ".join(str(n) for n in case["needsReview"]) or "none",
            "lanes": " ".join(case["lanes"]),
        })
    return rows


def page_text(url):
    req = urllib.request.Request(url, headers={"User-Agent": UA})
    with urllib.request.urlopen(req, timeout=30) as resp:
        raw = resp.read().decode("utf-8", "replace")
    text = html.unescape(re.sub(r"<[^>]+>", " ", raw))
    return re.sub(r"\s+", " ", text).replace("’", "'")


def check_quotes():
    """0 when every quote is found, 1 when one is gone, 2 when a page could not be read."""
    status = 0
    for url, quotes in QUOTES.items():
        try:
            text = page_text(url)
        except (urllib.error.URLError, TimeoutError) as error:
            print(f"UNREACHABLE {url}: {error}")
            status = max(status, 2)
            continue
        for quote in quotes:
            if quote not in text:
                print(f"FAIL quote gone from {url}: {quote!r}")
                status = 1
    return status


def main():
    rows = measure()
    target = HERE / "dataset.csv"
    if "--write" in sys.argv:
        with target.open("w", newline="") as f:
            w = csv.DictWriter(f, fieldnames=FIELDS)
            w.writeheader()
            w.writerows(rows)

    failed = False
    with target.open(newline="") as f:
        stored = list(csv.DictReader(f))
    if stored != rows:
        failed = True
        stored_by_id = {r["id"]: r for r in stored}
        for row in rows:
            if stored_by_id.get(row["id"]) != row:
                print(f'FAIL {row["id"]}: now {row}, stored {stored_by_id.get(row["id"])}')
        if len(stored) != len(rows):
            print(f"FAIL {len(rows)} cases measured, {len(stored)} stored")

    quotes = check_quotes()
    if failed or quotes == 1:
        return 1
    if quotes == 2:
        return 2
    print(f"ok: {len(rows)} cases match dataset.csv, every quote found")
    return 0


if __name__ == "__main__":
    sys.exit(main())
