#!/usr/bin/env python3
"""Fail-closed loader for the SCRNA-TELEGRAPH corrected burst-kinetics tables.

Every file is sha256-verified against MANIFEST.json BEFORE any content is
returned. A checksum mismatch raises ChecksumMismatchError; a missing file or
manifest raises ManifestError. There is no partial or unverified load path.

Usage:
    from load_corrected_tables import load_csv, load_jsonl, verify_all
    rows = load_csv("fibro_CAST")        # list of dict rows (strings as-is)
    prov, genes = load_jsonl("fibro_CAST")
    verify_all()                          # sha-verify every manifest file

Stdlib only. For pandas use:
    pd.read_csv(verified_path("burst_kinetics_fibro_CAST.csv"),
                comment="#", float_precision="round_trip")
(verified_path() sha-verifies before returning the path.)
"""
import csv
import hashlib
import io
import json
import os

HERE = os.path.dirname(os.path.abspath(__file__))
MANIFEST_PATH = os.path.join(HERE, "MANIFEST.json")
DATASETS = ("fibro_C57", "fibro_CAST", "mESC_CAST")


class ManifestError(RuntimeError):
    """Manifest or listed file missing/unparseable — nothing was loaded."""


class ChecksumMismatchError(RuntimeError):
    """A file's sha256 does not match MANIFEST.json — nothing was loaded."""


def _manifest():
    if not os.path.exists(MANIFEST_PATH):
        raise ManifestError(f"MANIFEST.json not found at {MANIFEST_PATH}")
    with open(MANIFEST_PATH) as f:
        m = json.load(f)
    if "files" not in m:
        raise ManifestError("MANIFEST.json has no 'files' block")
    return m


def _verified_bytes(basename):
    m = _manifest()
    entry = m["files"].get(basename)
    if entry is None:
        raise ManifestError(f"{basename} is not listed in MANIFEST.json")
    path = os.path.join(HERE, basename)
    if not os.path.exists(path):
        raise ManifestError(f"listed file missing on disk: {path}")
    data = open(path, "rb").read()
    got = hashlib.sha256(data).hexdigest()
    if got != entry["sha256"]:
        raise ChecksumMismatchError(
            f"{basename}: sha256 {got} != manifest {entry['sha256']}")
    return data, entry


def verified_path(basename):
    """sha-verify basename against the manifest, then return its path."""
    _verified_bytes(basename)
    return os.path.join(HERE, basename)


def load_csv(dataset):
    """Verified CSV rows as list-of-dicts (values kept as strings)."""
    if dataset not in DATASETS:
        raise ManifestError(f"unknown dataset {dataset!r}; choose from {DATASETS}")
    data, _ = _verified_bytes(f"burst_kinetics_{dataset}.csv")
    lines = [l for l in io.StringIO(data.decode()) if not l.startswith("#")]
    return list(csv.DictReader(lines))


def load_jsonl(dataset):
    """Verified JSONL -> (provenance_record, [gene_records])."""
    if dataset not in DATASETS:
        raise ManifestError(f"unknown dataset {dataset!r}; choose from {DATASETS}")
    data, entry = _verified_bytes(f"burst_kinetics_{dataset}.jsonl")
    records = [json.loads(l) for l in io.StringIO(data.decode()) if l.strip()]
    if not records or records[0].get("record_type") != "provenance":
        raise ManifestError(f"{dataset}: first JSONL record is not provenance")
    genes = records[1:]
    if len(genes) != entry["gene_rows"]:
        raise ChecksumMismatchError(
            f"{dataset}: {len(genes)} gene records != manifest {entry['gene_rows']}")
    return records[0], genes


def verify_all():
    """sha-verify every file in the manifest; returns {basename: sha256}."""
    m = _manifest()
    out = {}
    for basename in sorted(m["files"]):
        if basename == os.path.basename(__file__):
            continue
        _, entry = _verified_bytes(basename)
        out[basename] = entry["sha256"]
    return out


if __name__ == "__main__":
    for name, sha in verify_all().items():
        print(f"verified  {sha[:16]}...  {name}")
    print("ALL FILES VERIFIED against MANIFEST.json")
