#!/usr/bin/env python3
"""Read-only inspection of three public Standard Ebooks EPUB packages.

Prints observations to stdout; does not save or redistribute ebook contents.
Python 3 standard library only. Network downloads remain in memory.
"""
import datetime
import hashlib
import io
import json
import platform
import posixpath
import subprocess
import xml.etree.ElementTree as ET
import zipfile

BASE = "https://standardebooks.org/ebooks/jane-austen/pride-and-prejudice"
NS = {"opf": "http://www.idpf.org/2007/opf", "dc": "http://purl.org/dc/elements/1.1/"}


def inspect(suffix):
    url = BASE + "/downloads/jane-austen_pride-and-prejudice" + suffix
    # The landing page advertises this exact URL in its HTML meta-refresh.
    download_url = url + "?source=download"
    payload = subprocess.check_output(["curl", "--fail", "--silent", "--show-error", "--location", download_url])
    with zipfile.ZipFile(io.BytesIO(payload)) as archive:
        bad = archive.testzip()
        if bad:
            raise ValueError("ZIP CRC failed: " + bad)
        container = ET.fromstring(archive.read("META-INF/container.xml"))
        package = container.find("{*}rootfiles/{*}rootfile").attrib["full-path"]
        root = ET.fromstring(archive.read(package))
        items = root.findall("opf:manifest/opf:item", NS)
        spine = root.findall("opf:spine/opf:itemref", NS)
        resources = {item.attrib["id"]: posixpath.normpath(posixpath.join(posixpath.dirname(package), item.attrib["href"])) for item in items}
        missing = [name for name in resources.values() if name not in archive.namelist()]
        xhtml = [name for name in archive.namelist() if name.endswith(".xhtml")]
        kobo_spans = 0
        for name in xhtml:
            tree = ET.fromstring(archive.read(name))
            kobo_spans += sum("koboSpan" in element.attrib.get("class", "").split() for element in tree.iter())
        return {
            "url": url, "download_url": download_url, "bytes": len(payload), "sha256": hashlib.sha256(payload).hexdigest(),
            "zip_crc": "pass", "package_version": root.attrib.get("version"),
            "title": root.findtext("opf:metadata/dc:title", namespaces=NS),
            "language": root.findtext("opf:metadata/dc:language", namespaces=NS),
            "manifest_items": len(items), "spine_items": len(spine),
            "missing_manifest_resources": missing,
            "missing_spine_ids": [item.attrib["idref"] for item in spine if item.attrib["idref"] not in resources],
            "xhtml_files": len(xhtml), "kobo_span_elements": kobo_spans,
            "svg_resources": sum(item.attrib["media-type"] == "image/svg+xml" for item in items),
            "png_resources": sum(item.attrib["media-type"] == "image/png" for item in items),
        }


if __name__ == "__main__":
    print(json.dumps({
        "observed_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "python": platform.python_version(), "source_record": BASE,
        "method": "Three sequential HTTPS downloads; ZIP CRC, XML package and local resource checks. No device rendering, EPUBCheck, text equivalence, completeness or translation test.",
        "files": {name: inspect(suffix) for name, suffix in [("compatible", ".epub"), ("advanced", "_advanced.epub"), ("kobo", ".kepub.epub")]},
    }, indent=2))
