from __future__ import annotations

import argparse
import csv
import hashlib
import json
from pathlib import Path


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Audit a locally downloaded sample of the CC BY 4.0 BLB UAV Dataset without redistributing it."
    )
    parser.add_argument("--source", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()

    try:
        import numpy as np
        import tifffile
    except ImportError as exc:  # pragma: no cover - environment guard
        raise SystemExit("Install numpy and tifffile before running the external sample audit") from exc

    expected = {
        "notebook": args.source / "BLB_prj_source_code.ipynb",
        "classes": args.source / "labels_class01234.csv",
        "image": args.source / "D1-test-image_patch_1002.tif",
        "label": args.source / "D1-test-label_patch_1002.tif",
    }
    missing = [str(path) for path in expected.values() if not path.is_file()]
    if missing:
        raise FileNotFoundError("Missing third-party inspection files: " + ", ".join(missing))

    with expected["classes"].open("r", encoding="utf-8-sig", newline="") as stream:
        class_rows = list(csv.DictReader(stream))
    image = tifffile.imread(expected["image"])
    label = tifffile.imread(expected["label"])
    unique_labels = sorted(int(value) for value in np.unique(label))

    checks = {
        "imageIs256SquareFiveBand": image.shape == (256, 256, 5),
        "imageIsFloatingPoint": bool(np.issubdtype(image.dtype, np.floating)),
        "imageWithinPublishedNormalisedRange": bool(np.isfinite(image).all() and image.min() >= 0 and image.max() <= 1),
        "labelIs256Square": label.shape == (256, 256),
        "labelValuesWithinPublishedDictionary": set(unique_labels).issubset({0, 1, 2, 3, 4}),
        "classDictionaryHasFiveRows": len(class_rows) == 5,
    }
    passed = all(checks.values())
    payload = {
        "schemaVersion": 1,
        "auditedAt": "2026-08-04",
        "source": {
            "title": "Bacterial Leaf Blight (BLB) UAV Dataset and U-Net with ResNet-101 Code for BLB Detection",
            "doi": "10.6084/m9.figshare.26955862.v3",
            "license": "CC BY 4.0",
            "sensor": "DJI Phantom 4 Multispectral",
            "redistributedByThisPackage": False,
        },
        "scope": "One deterministic D1 test image-mask pair, the class dictionary, and the source notebook were inspected locally. This is a format compatibility check, not a performance evaluation.",
        "files": {
            key: {"name": path.name, "bytes": path.stat().st_size, "sha256": sha256(path)}
            for key, path in expected.items()
        },
        "observed": {
            "imageShape": list(image.shape),
            "imageDtype": str(image.dtype),
            "imageMinimum": float(image.min()),
            "imageMaximum": float(image.max()),
            "labelShape": list(label.shape),
            "labelDtype": str(label.dtype),
            "uniqueLabelValues": unique_labels,
            "classRows": class_rows,
            "publishedBandOrderD1": ["blue", "green", "red", "red_edge", "nir"],
            "mavic3mTransferSelectionZeroBased": [1, 2, 3, 4],
        },
        "checks": checks,
        "status": "PASSED" if passed else "FAILED",
        "claimBoundary": "A passing result only verifies the inspected file contract. It does not validate a Mavic 3M disease model, field accuracy, cross-sensor transfer, or pathogen diagnosis.",
    }
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"status": payload["status"], "output": str(args.output)}, ensure_ascii=False))
    if not passed:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
