"""
Pipeline driver.

Runs the analysis stages in order and reports, for each, whether it was
regenerated or read from the deposited outputs. The distinction matters and is
printed explicitly: some stages need the third-party CORAL supplementary table,
which is not redistributed here, and some are long-running.

    python3 code/run_analysis.py                 # what can run without CORAL
    CORAL_XLSX=/path/to/media-1.xlsx \
        python3 code/run_analysis.py             # the full pipeline
    python3 code/run_analysis.py --list          # stages and their inputs

Stages that cannot run report why, rather than failing silently or being
skipped without notice.
"""

import argparse
import os
import subprocess
import sys
import time

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
DATA = os.path.join(ROOT, "data")
CORAL = os.environ.get("CORAL_XLSX", os.path.join(DATA, "media-1.xlsx"))

# name, script, needs the external table, produces, roughly how long
STAGES = [
    ("pipeline validation", "codecore.py", False,
     "reproduces the benchmark: Phi_SGC = 5.194, 3 of 10,000 better", "~1 min",
     []),
    ("tree and dataset integrity", "tree_check.py", False,
     "confirms 4,972 species resolve on the calibrated tree", "~2 min",
     ["data/timetree_5053_species.nwk"]),
    ("figures", "make_figures.py", True,
     "figures/Fig1-4 in EPS, PDF and PNG", "~2 min", []),
    ("numerical audit", "audit_numbers.py", False,
     "checks every asserted quantity against the deposited outputs", "~1 min",
     []),
]

DEPOSITED = [
    ("cohortB_R_per_species.csv", "R, z, p_emp and sigma^2 for 5,053 species"),
    ("confirmatory_aligned.csv", "the 4,972-species confirmatory set"),
    ("spectrum_components.csv", "CpG>T shift and kappa per species"),
    ("R_all_metrics.csv", "R under all three amino-acid metrics"),
    ("per_clade_slopes_phylo.csv", "beta_kappa with profile-likelihood intervals"),
    ("rmse_decomposition.csv", "out-of-clade bias and dispersion"),
    ("inverted_loco.csv", "inverted leave-one-clade-out"),
    ("nonsense_share.csv", "nonsense burden per species"),
    ("ensemble_summaries.npz", "per-code slope, mean and variance of R"),
    ("confirmatory_ensemble.json", "the frozen 10,000-code ensemble"),
]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--list", action="store_true",
                    help="show the stages and what each needs, then exit")
    args = ap.parse_args()

    have_coral = os.path.isfile(CORAL)

    print("=" * 72)
    print("REPRODUCTION PIPELINE")
    print("=" * 72)
    print(f"package root : {ROOT}")
    print(f"CORAL table  : {'found at ' + CORAL if have_coral else 'NOT FOUND'}")
    if not have_coral:
        print("               set CORAL_XLSX to the source study's "
              "supplementary table")
    print()

    print("Deposited outputs (read, not regenerated by this driver):")
    for name, what in DEPOSITED:
        mark = "ok " if os.path.isfile(os.path.join(DATA, name)) else "MISSING"
        print(f"  [{mark}] {name:<32} {what}")
    print()
    print("  These are the products of the modelling stages — the phylogenetic")
    print("  mixed model, the parametric bootstrap, the cross-validation and")
    print("  the annealing search. Regenerating them end to end takes hours and")
    print("  needs the CORAL table; the driver below verifies them rather than")
    print("  recomputing them. Their provenance is the code in this directory.")
    print()

    if args.list:
        print("Stages:")
        for name, script, needs, produces, cost, args in STAGES:
            print(f"  {name:<28} {script:<20} "
                  f"{'needs CORAL' if needs else 'self-contained':<15} {cost}")
        return 0

    print("=" * 72)
    failed = []
    for name, script, needs, produces, cost, args in STAGES:
        path = os.path.join(HERE, script)
        print(f"\n>>> {name}  ({script}, {cost})")
        print(f"    produces: {produces}")
        if needs and not have_coral:
            print("    SKIPPED - requires the CORAL supplementary table")
            continue
        if not os.path.isfile(path):
            print("    MISSING - script not in the package")
            failed.append(name)
            continue
        t0 = time.time()
        r = subprocess.run([sys.executable, path] +
                           [os.path.join(ROOT, a) for a in args],
                           cwd=ROOT, capture_output=True, text=True)
        tail = [l for l in r.stdout.strip().split("\n") if l.strip()][-3:]
        for line in tail:
            print("    " + line)
        if r.returncode != 0:
            print(f"    FAILED after {time.time()-t0:.0f}s")
            if r.stderr:
                print("    " + r.stderr.strip().split("\n")[-1][:100])
            failed.append(name)
        else:
            print(f"    ok ({time.time()-t0:.0f}s)")

    print("\n" + "=" * 72)
    if failed:
        print("FAILED: " + ", ".join(failed))
        return 1
    print("All runnable stages completed.")
    if not have_coral:
        print("Stages needing the CORAL table were skipped and are listed above.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
