#!/usr/bin/env python3
"""Audit every public ENA human WXS CRAM/CRAI pair."""

from __future__ import annotations

import csv
import json
import statistics
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from pathlib import Path

from audit_public_wes_alignments import https_from_ftpish, range_probe, split_multi


DATE = "2026-08-01"


def main() -> int:
    if len(sys.argv) != 2:
        raise SystemExit("usage: audit_ena_wxs_cram.py ANALYSIS_DIR")
    analysis = Path(sys.argv[1])
    source = analysis / f"ena_human_wxs_submitted_cram_inventory_{DATE}.tsv"
    rows: list[dict[str, object]] = []
    with source.open("r", encoding="utf-8", newline="") as fh:
        for raw in csv.DictReader(fh, delimiter="\t"):
            fmts = split_multi(raw["submitted_format"])
            urls = split_multi(raw["submitted_ftp"])
            sizes = split_multi(raw["submitted_bytes"])
            md5s = split_multi(raw["submitted_md5"])
            objects = [
                {
                    "format": fmt.upper(),
                    "url": https_from_ftpish(urls[i]),
                    "bytes": int(sizes[i]),
                    "md5": md5s[i],
                }
                for i, fmt in enumerate(fmts)
            ]
            crams = [o for o in objects if o["format"] == "CRAM"]
            crais = [o for o in objects if o["format"] == "CRAI"]
            if len(crams) != 1 or len(crais) != 1:
                raise ValueError(f"unexpected ENA CRAM row: {raw['run_accession']} {fmts}")
            cram, crai = crams[0], crais[0]
            rows.append(
                {
                    "repository": "ENA",
                    "collection": raw["study_accession"],
                    "run_accession": raw["run_accession"],
                    "sample_id": raw["sample_accession"],
                    "reference_build": "submitted; inspect CRAM header/reference MD5",
                    "platform_or_kit": "submitted WXS; capture kit unknown in this query",
                    "alignment_format": "CRAM",
                    "alignment_url": cram["url"],
                    "index_url": crai["url"],
                    "alignment_bytes": cram["bytes"],
                    "index_bytes": crai["bytes"],
                    "alignment_checksum": cram["md5"],
                    "alignment_checksum_algorithm": "MD5",
                    "index_checksum": crai["md5"],
                    "index_checksum_algorithm": "MD5",
                    "origin_note": "ENA read_run metadata: Homo sapiens; library_strategy=WXS; submitted CRAM+CRAI",
                }
            )

    tasks = {}
    progress_path = analysis / f"ena_wxs_cram_probe_progress_{DATE}.json"
    started = time.time()
    completed = ok = 0
    with ThreadPoolExecutor(max_workers=32) as pool:
        for i, row in enumerate(rows):
            tasks[pool.submit(range_probe, str(row["alignment_url"]), int(row["alignment_bytes"]))] = (i, "alignment")
            tasks[pool.submit(range_probe, str(row["index_url"]), int(row["index_bytes"]))] = (i, "index")
        for future in as_completed(tasks):
            i, kind = tasks[future]
            result = future.result()
            rows[i][kind + "_probe"] = result
            completed += 1
            ok += int(result["ok"])
            if completed % 250 == 0 or completed == len(tasks):
                progress = {
                    "status": "running" if completed < len(tasks) else "complete",
                    "tasks_done": completed,
                    "tasks_total": len(tasks),
                    "tasks_ok": ok,
                    "elapsed_seconds": round(time.time() - started, 1),
                    "updated_at": datetime.now(timezone.utc).isoformat(),
                }
                progress_path.write_text(json.dumps(progress, indent=2), encoding="utf-8")
                print(json.dumps(progress), flush=True)

    for row in rows:
        row["verified_downloadable"] = bool(
            row["alignment_probe"].get("ok") and row["index_probe"].get("ok")  # type: ignore[union-attr]
        )
        size = int(row["alignment_bytes"])
        row["size_review_flag"] = (
            "very_small_under_100MiB" if size < 100 * 1024**2
            else "small_under_1GiB" if size < 1024**3
            else "plausible_full_WXS_by_size"
        )

    all_path = analysis / f"ena_human_wxs_cram_all_candidates_{DATE}.csv"
    verified_path = analysis / f"ena_human_wxs_cram_downloadable_verified_{DATE}.csv"
    fields = [
        "repository", "collection", "run_accession", "sample_id", "reference_build",
        "platform_or_kit", "alignment_format", "alignment_url", "index_url",
        "alignment_bytes", "index_bytes", "alignment_checksum",
        "alignment_checksum_algorithm", "index_checksum", "index_checksum_algorithm",
        "alignment_http_status", "index_http_status", "alignment_content_range",
        "index_content_range", "verified_downloadable", "size_review_flag", "origin_note",
    ]

    def output(path: Path, selected: list[dict[str, object]]) -> None:
        with path.open("w", encoding="utf-8", newline="") as fh:
            writer = csv.DictWriter(fh, fieldnames=fields)
            writer.writeheader()
            for row in selected:
                writer.writerow(
                    {
                        **{k: row.get(k, "") for k in fields},
                        "alignment_http_status": row["alignment_probe"].get("status"),  # type: ignore[union-attr]
                        "index_http_status": row["index_probe"].get("status"),  # type: ignore[union-attr]
                        "alignment_content_range": row["alignment_probe"].get("content_range"),  # type: ignore[union-attr]
                        "index_content_range": row["index_probe"].get("content_range"),  # type: ignore[union-attr]
                    }
                )

    verified = [row for row in rows if row["verified_downloadable"]]
    output(all_path, rows)
    output(verified_path, verified)
    sizes = [int(row["alignment_bytes"]) for row in rows]
    bins = {
        "under_100_MiB": sum(n < 100 * 1024**2 for n in sizes),
        "100_MiB_to_under_1_GiB": sum(100 * 1024**2 <= n < 1024**3 for n in sizes),
        "1_to_under_2_GiB": sum(1024**3 <= n < 2 * 1024**3 for n in sizes),
        "2_to_under_5_GiB": sum(2 * 1024**3 <= n < 5 * 1024**3 for n in sizes),
        "5_to_under_10_GiB": sum(5 * 1024**3 <= n < 10 * 1024**3 for n in sizes),
        "10_GiB_or_more": sum(n >= 10 * 1024**3 for n in sizes),
    }
    payload = {
        "audit_date": DATE,
        "source_metadata": source.name,
        "official_query": 'tax_eq(9606) AND library_strategy="WXS" AND submitted_format="CRAM"',
        "candidate_pairs": len(rows),
        "verified_pairs": len(verified),
        "failed_pairs": len(rows) - len(verified),
        "studies": len({str(row["collection"]) for row in rows}),
        "sample_accessions": len({str(row["sample_id"]) for row in rows}),
        "total_alignment_bytes": sum(sizes),
        "min_alignment_bytes": min(sizes),
        "median_alignment_bytes": int(statistics.median(sizes)),
        "max_alignment_bytes": max(sizes),
        "size_bins": bins,
        "unknown_build_count": len(rows),
        "unknown_capture_count": len(rows),
        "verification": "anonymous HTTP GET Range bytes=0-0 on every CRAM and CRAI, with Content-Range total matched to ENA metadata",
        "all_candidates_csv": all_path.name,
        "downloadable_only_csv": verified_path.name,
        "failures": [
            {"run_accession": row["run_accession"], "alignment_url": row["alignment_url"], "index_url": row["index_url"], "alignment_probe": row["alignment_probe"], "index_probe": row["index_probe"]}
            for row in rows if not row["verified_downloadable"]
        ],
    }
    json_path = analysis / f"ena_human_wxs_cram_repository_audit_{DATE}.json"
    json_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    print(json.dumps({"status": "complete", "json": str(json_path), "verified": len(verified)}), flush=True)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
