#!/usr/bin/env python3
"""Refresh first-pass audit summaries after low-concurrency retry."""

from __future__ import annotations

import csv
import json
import statistics
import sys
from collections import Counter, defaultdict
from datetime import datetime, timezone
from pathlib import Path


DATE = "2026-08-01"


def truthy(value: object) -> bool:
    return str(value or "").strip().lower() in {"true", "1", "yes"}


def integer(value: object) -> int | None:
    text = str(value or "").strip()
    return int(text) if text.isdigit() else None


def read_csv(path: Path) -> tuple[list[str], list[dict[str, str]]]:
    with path.open("r", encoding="utf-8", newline="") as fh:
        reader = csv.DictReader(fh)
        return list(reader.fieldnames or []), list(reader)


def write_csv(path: Path, fields: list[str], rows: list[dict[str, str]]) -> None:
    with path.open("w", encoding="utf-8", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)


def failure_record(row: dict[str, str], indexed: bool = True) -> dict[str, object]:
    expected_a = integer(row.get("alignment_bytes"))
    observed_a = integer(row.get("alignment_observed_bytes"))
    expected_i = integer(row.get("index_bytes"))
    observed_i = integer(row.get("index_observed_bytes"))
    return {
        "repository": row.get("repository", "ENA"),
        "collection": row.get("collection", ""),
        "run_accession": row.get("run_accession", ""),
        "alignment_url": row.get("alignment_url", ""),
        "index_url": row.get("index_url", "") if indexed else "",
        "alignment_http_status": row.get("alignment_http_status", ""),
        "index_http_status": row.get("index_http_status", "") if indexed else "",
        "alignment_expected_bytes": expected_a,
        "alignment_observed_bytes": observed_a,
        "index_expected_bytes": expected_i if indexed else None,
        "index_observed_bytes": observed_i if indexed else None,
        "failure_class": "metadata_path_did_not_resolve_to_metadata_sized_file_object",
        "evidence_note": "HTTP response total did not match official submitted_bytes; small responses were excluded rather than counted as genomic files.",
    }


def main() -> int:
    if len(sys.argv) != 2:
        raise SystemExit("usage: refresh_public_wes_audit_summaries.py ANALYSIS_DIR")
    analysis = Path(sys.argv[1])
    now = datetime.now(timezone.utc).isoformat()

    # Broad BAM/non-ENA audit.
    main_csv = analysis / f"public_complete_wes_alignment_inventory_verified_{DATE}.csv"
    main_fields, main_rows = read_csv(main_csv)
    main_verified = [row for row in main_rows if truthy(row.get("verified_downloadable"))]
    main_failed = [row for row in main_rows if not truthy(row.get("verified_downloadable"))]
    main_verified_csv = analysis / f"public_complete_wes_alignment_downloadable_only_{DATE}.csv"
    write_csv(main_verified_csv, main_fields, main_verified)
    main_json = analysis / f"public_complete_wes_repository_audit_{DATE}.json"
    main_report = json.loads(main_json.read_text(encoding="utf-8"))
    main_report["generated_at_utc"] = now
    main_report["verification_method"] = "Anonymous HTTP GET Range bytes=0-0 on every alignment and index, followed by an eight-attempt low-concurrency retry for every first-pass failure; official expected size had to match Content-Range total."
    main_report["totals"]["candidate_alignment_objects"] = len(main_rows)
    main_report["totals"]["verified_alignment_and_index_pairs"] = len(main_verified)
    main_report["totals"]["failed_pairs"] = len(main_failed)
    grouped: dict[str, list[dict[str, str]]] = defaultdict(list)
    for row in main_rows:
        grouped[row["repository"]].append(row)
    for repo, rows in grouped.items():
        verified = [row for row in rows if truthy(row.get("verified_downloadable"))]
        item = main_report["repositories"].setdefault(repo, {})
        item["candidate_alignment_objects"] = len(rows)
        item["verified_alignment_and_index_pairs"] = len(verified)
        item["failed_pairs"] = len(rows) - len(verified)
        item["range_status_pairs_after_retry"] = dict(Counter(
            f"{row.get('alignment_http_status','')}+{row.get('index_http_status','')}" for row in verified
        ))
    main_report["failures"] = [failure_record(row) for row in main_failed]
    main_report.setdefault("artifacts", {})["downloadable_only_csv"] = main_verified_csv.name
    main_report["retry_reconciliation"] = {
        "first_pass_verified": 5410,
        "final_verified": len(main_verified),
        "persistent_failures": len(main_failed),
        "failure_policy": "Never count an HTTP 206 response when Content-Range total disagrees with official metadata size.",
    }
    main_json.write_text(json.dumps(main_report, indent=2), encoding="utf-8")

    # ENA CRAM/CRAI audit.
    cram_csv = analysis / f"ena_human_wxs_cram_all_candidates_{DATE}.csv"
    cram_fields, cram_rows = read_csv(cram_csv)
    cram_verified = [row for row in cram_rows if truthy(row.get("verified_downloadable"))]
    cram_failed = [row for row in cram_rows if not truthy(row.get("verified_downloadable"))]
    cram_verified_csv = analysis / f"ena_human_wxs_cram_downloadable_verified_{DATE}.csv"
    write_csv(cram_verified_csv, cram_fields, cram_verified)
    cram_json = analysis / f"ena_human_wxs_cram_repository_audit_{DATE}.json"
    cram_report = json.loads(cram_json.read_text(encoding="utf-8"))
    cram_report.update({
        "generated_at_utc": now,
        "candidate_pairs": len(cram_rows),
        "verified_pairs": len(cram_verified),
        "failed_pairs": len(cram_failed),
        "verification": "Anonymous bytes=0-0 HTTP range GET for every CRAM and CRAI plus low-concurrency eight-attempt retry; Content-Range total had to equal ENA submitted_bytes.",
        "downloadable_only_csv": cram_verified_csv.name,
        "failures": [failure_record(row) for row in cram_failed],
        "retry_reconciliation": {
            "first_pass_verified": 5919,
            "final_verified": len(cram_verified),
            "persistent_failures": len(cram_failed),
        },
    })
    cram_json.write_text(json.dumps(cram_report, indent=2), encoding="utf-8")

    # ENA BAMs without BAI.
    unindexed_csv = analysis / f"ena_human_wxs_bam_without_index_all_candidates_{DATE}.csv"
    unindexed_fields, unindexed_rows = read_csv(unindexed_csv)
    unindexed_verified = [row for row in unindexed_rows if truthy(row.get("verified_downloadable"))]
    unindexed_failed = [row for row in unindexed_rows if not truthy(row.get("verified_downloadable"))]
    unindexed_verified_csv = analysis / f"ena_human_wxs_bam_without_index_downloadable_verified_{DATE}.csv"
    write_csv(unindexed_verified_csv, unindexed_fields, unindexed_verified)
    unindexed_json = analysis / f"ena_human_wxs_bam_without_index_audit_{DATE}.json"
    unindexed_report = json.loads(unindexed_json.read_text(encoding="utf-8"))
    sizes = [int(row["alignment_bytes"]) for row in unindexed_verified]
    unindexed_report.update({
        "generated_at_utc": now,
        "candidate_bams_without_index": len(unindexed_rows),
        "verified_downloadable_bams_without_index": len(unindexed_verified),
        "failed": len(unindexed_failed),
        "verified_alignment_bytes_total": sum(sizes),
        "verified_alignment_bytes_median": int(statistics.median(sizes)) if sizes else None,
        "csv": unindexed_verified_csv.name,
        "failures": [failure_record(row, indexed=False) for row in unindexed_failed],
    })
    unindexed_json.write_text(json.dumps(unindexed_report, indent=2), encoding="utf-8")

    print(json.dumps({
        "main_verified": len(main_verified), "main_failed": len(main_failed),
        "cram_verified": len(cram_verified), "cram_failed": len(cram_failed),
        "unindexed_verified": len(unindexed_verified), "unindexed_failed": len(unindexed_failed),
    }), flush=True)
    return 0


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