#!/usr/bin/env python3
"""Re-checks the post "How to import bank transactions into QuickBooks Desktop".

Two kinds of fact, both listed in dataset.json:
  1. product code: each `expect` string is still present in its file in the Evidilo repository;
  2. Intuit help pages: each recorded quote is still on the page it was taken from.

Exit codes: 0 everything holds; 1 a quote is gone, a code fact changed or the summary counts
disagree with the inputs; 2 nothing failed but at least one Intuit page could not be reached.

--recheck re-reads the cited pages (there are no lab steps to skip, so it does the same as a
plain run). Run with cwd = this directory.
"""
import html
import json
import re
import sys
import unicodedata
import urllib.error
import urllib.request
from pathlib import Path

HERE = Path(__file__).resolve().parent
UA = (
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
)


def normalise(text: str) -> str:
    text = unicodedata.normalize("NFKC", html.unescape(text))
    for a, b in (("’", "'"), ("‘", "'"), ("“", '"'), ("”", '"'), (" ", " ")):
        text = text.replace(a, b)
    text = re.sub(r"\s+", " ", text)
    # A tag closing right before punctuation leaves "Bank Feeds ," once tags become spaces.
    return re.sub(r" ([.,;:!?])", r"\1", text).strip().lower()


def page_text(raw: str) -> str:
    raw = re.sub(r"(?is)<(script|style)[^>]*>.*?</\1>", " ", raw)
    raw = re.sub(r"(?s)<[^>]+>", " ", raw)
    return normalise(raw)


def fetch(url: str) -> str:
    req = urllib.request.Request(
        url,
        headers={"User-Agent": UA, "Accept": "text/html,application/xhtml+xml", "Accept-Language": "en-US,en;q=0.9"},
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        return resp.read().decode("utf-8", errors="replace")


def main() -> int:
    data = json.loads((HERE / "dataset.json").read_text(encoding="utf-8"))
    failed = False
    unreachable = []

    # The repository root is the first ancestor holding packages/exports.
    repo = next((p for p in HERE.parents if (p / "packages" / "exports").is_dir()), None)
    if repo is None:
        print("FAIL repository root with packages/exports not found above", HERE)
        return 1

    for item in data["inputs"]["code"]:
        source = (repo / item["path"]).read_text(encoding="utf-8")
        ok = item["expect"] in source
        print(("ok  " if ok else "FAIL") + f" code {item['id']}: {item['path']}")
        failed |= not ok

    pages = data["inputs"]["pages"]
    total_quotes = sum(len(p["quotes"]) for p in pages)
    summary = data["results"]["summary"]
    routes = data["results"]["routes"]
    counts = {
        "intuitPagesFetched": len(pages),
        "quotesRecorded": total_quotes,
        "routesDocumented": len(routes),
        "routesThatTakeBankRows": sum(1 for r in routes if r["takesBankRows"]),
        "productCodeFactsChecked": len(data["inputs"]["code"]),
    }
    for key, value in counts.items():
        ok = summary[key] == value
        print(("ok  " if ok else "FAIL") + f" summary {key} = {summary[key]} (inputs give {value})")
        failed |= not ok
    known = {p["id"] for p in pages}
    for r in routes:
        missing = [s for s in r["sources"] if s not in known]
        if missing:
            print(f"FAIL route {r['route']} cites unknown page(s) {missing}")
            failed = True

    for page in pages:
        if unreachable and len(unreachable) >= 2:
            # Two pages in a row timed out on the same host: the rest would wait for nothing.
            print(f"UNREACHABLE {page['id']}: skipped, quickbooks.intuit.com not answering")
            unreachable.append(page["id"])
            continue
        try:
            text = page_text(fetch(page["url"]))
        except (urllib.error.URLError, TimeoutError, OSError) as exc:
            print(f"UNREACHABLE {page['id']}: {page['url']} ({exc})")
            unreachable.append(page["id"])
            continue
        for quote in page["quotes"]:
            ok = normalise(quote) in text
            print(("ok  " if ok else "FAIL") + f" {page['id']}: {quote[:80]}")
            failed |= not ok

    if failed:
        print("RESULT: a fact no longer holds")
        return 1
    if unreachable:
        print(f"RESULT: {len(unreachable)} of {len(pages)} Intuit pages could not be reached; quotes on them unconfirmed")
        return 2
    print("RESULT: everything holds")
    return 0


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