#!/usr/bin/env python3
"""Verify ENA human WXS BAM submissions that lack a submitted BAI."""

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_bam_unindexed.py ANALYSIS_DIR")
    analysis = Path(sys.argv[1])
    source = analysis / f"ena_human_wxs_submitted_bam_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"):
            formats = [x.upper() for x in split_multi(raw["submitted_format"])]
            if "BAM" not in formats or "BAI" in formats:
                continue
            urls = split_multi(raw["submitted_ftp"])
            sizes = split_multi(raw["submitted_bytes"])
            md5s = split_multi(raw["submitted_md5"])
            for i, fmt in enumerate(formats):
                if fmt != "BAM":
                    continue
                rows.append(
                    {
                        "repository": "ENA",
                        "collection": raw["study_accession"],
                        "run_accession": raw["run_accession"],
                        "sample_id": raw["sample_accession"],
                        "alignment_format": "BAM",
                        "alignment_url": https_from_ftpish(urls[i]),
                        "alignment_bytes": int(sizes[i]),
                        "alignment_md5": md5s[i],
                        "index_url": "",
                        "reference_build": "submitted; inspect BAM header",
                        "capture": "submitted WXS; unknown in this query",
                        "limitation": "No submitted BAI: full download and local samtools index are required before random-access analysis.",
                    }
                )
    started = time.time()
    progress_path = analysis / f"ena_wxs_bam_unindexed_probe_progress_{DATE}.json"
    tasks = {}
    done = ok = 0
    with ThreadPoolExecutor(max_workers=16) as pool:
        for i, row in enumerate(rows):
            tasks[pool.submit(range_probe, str(row["alignment_url"]), int(row["alignment_bytes"]))] = i
        for future in as_completed(tasks):
            i = tasks[future]
            result = future.result()
            rows[i]["alignment_probe"] = result
            done += 1
            ok += int(result["ok"])
            if done % 100 == 0 or done == len(tasks):
                progress = {"status": "running" if done < len(tasks) else "complete", "tasks_done": done, "tasks_total": len(tasks), "tasks_ok": ok, "elapsed_seconds": round(time.time() - started, 1)}
                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"))  # 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"

    verified = [row for row in rows if row["verified_downloadable"]]
    csv_path = analysis / f"ena_human_wxs_bam_without_index_downloadable_verified_{DATE}.csv"
    all_csv_path = analysis / f"ena_human_wxs_bam_without_index_all_candidates_{DATE}.csv"
    fields = ["repository", "collection", "run_accession", "sample_id", "alignment_format", "alignment_url", "alignment_bytes", "alignment_md5", "index_url", "reference_build", "capture", "alignment_http_status", "alignment_content_range", "alignment_observed_bytes", "alignment_probe_bytes_received", "alignment_probe_error", "alignment_range_result", "verified_downloadable", "size_review_flag", "limitation"]

    def write_csv(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:
                probe = row["alignment_probe"]  # type: ignore[assignment]
                status = probe.get("status")  # type: ignore[union-attr]
                writer.writerow({
                    **{k: row.get(k, "") for k in fields},
                    "alignment_http_status": status,
                    "alignment_content_range": probe.get("content_range"),  # type: ignore[union-attr]
                    "alignment_observed_bytes": probe.get("observed_total_bytes"),  # type: ignore[union-attr]
                    "alignment_probe_bytes_received": probe.get("bytes_received"),  # type: ignore[union-attr]
                    "alignment_probe_error": probe.get("error", ""),  # type: ignore[union-attr]
                    "alignment_range_result": "HTTP_206_range_honored" if status == 206 else "HTTP_200_range_ignored" if status == 200 else "probe_failed",
                })

    write_csv(all_csv_path, rows)
    write_csv(csv_path, verified)
    sizes = [int(row["alignment_bytes"]) for row in rows]
    payload = {
        "audit_date": DATE,
        "candidate_bams_without_index": len(rows),
        "verified_downloadable_bams_without_index": len(verified),
        "failed": 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": {
            "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_GiB_or_more": sum(n >= 1024**3 for n in sizes),
        },
        "interpretation": "These are real public WXS BAM submissions, but not immediately usable for remote random access because no BAI was submitted. They are not included in the ready indexed total.",
        "csv": csv_path.name,
        "all_candidates_csv": all_csv_path.name,
        "failures": [{"run_accession": row["run_accession"], "url": row["alignment_url"], "probe": row["alignment_probe"]} for row in rows if not row["verified_downloadable"]],
    }
    json_path = analysis / f"ena_human_wxs_bam_without_index_audit_{DATE}.json"
    json_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    print(json.dumps({"status": "complete", "verified": len(verified), "json": str(json_path)}), flush=True)
    return 0


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