#!/usr/bin/env python3
"""Validate real SPARC chamber inputs without executing either chamber."""

from __future__ import annotations

import argparse
import hashlib
import sys
from pathlib import Path

import numpy as np
import pandas as pd


def read_csv_values(path: Path) -> np.ndarray:
    frame = pd.read_csv(path, float_precision="round_trip")
    if list(frame.columns) != ["log10_g_bar"]:
        raise ValueError(f"{path}: expected one column named log10_g_bar")
    return pd.to_numeric(frame["log10_g_bar"], errors="raise").to_numpy(dtype=float)


def read_txt_values(path: Path) -> np.ndarray:
    return np.array([float(value) for value in path.read_text(encoding="utf-8").split()], dtype=float)


def values_hash(values: np.ndarray) -> str:
    canonical = "\n".join(f"{value:.17g}" for value in values) + "\n"
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def parse_args() -> argparse.Namespace:
    root = Path(__file__).resolve().parents[1]
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--project-root", type=Path, default=root)
    parser.add_argument("--manifest", type=Path, default=root / "ladders/ladder_manifest.csv")
    parser.add_argument("--profiles", type=Path, default=root / "data/prepared_profiles/galaxy_profiles.csv")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    manifest = pd.read_csv(args.manifest)
    profiles = pd.read_csv(args.profiles, float_precision="round_trip")
    errors: list[str] = []
    if manifest.empty:
        errors.append("ladder manifest is empty")
    if manifest["galaxy_id"].nunique() != 175:
        errors.append(f"expected 175 manifest galaxies, found {manifest['galaxy_id'].nunique()}")

    profile_counts = profiles.groupby("galaxy_id").size()
    for row in manifest.itertuples(index=False):
        if row.status != "READY":
            errors.append(f"{row.galaxy_id}: status is {row.status}")
            continue
        csv_path = args.project_root / row.struc_i_input
        txt_path = args.project_root / row.struc_perc_i_input
        if not csv_path.exists() or not txt_path.exists():
            errors.append(f"{row.galaxy_id}: missing chamber input")
            continue
        csv_values = read_csv_values(csv_path)
        txt_values = read_txt_values(txt_path)
        expected = np.unique(np.sort(np.log10(
            profiles.loc[profiles["galaxy_id"] == row.galaxy_id, "g_bar"].to_numpy(dtype=float)
        )))
        if not np.array_equal(csv_values, txt_values):
            errors.append(f"{row.galaxy_id}: cross-chamber values differ")
        if not np.array_equal(csv_values, expected):
            errors.append(f"{row.galaxy_id}: ladder differs from observed prepared g_bar values")
        if len(csv_values) < 3:
            errors.append(f"{row.galaxy_id}: fewer than 3 ladder values")
        if not np.all(np.isfinite(csv_values)) or not np.all(np.diff(csv_values) > 0):
            errors.append(f"{row.galaxy_id}: ladder not finite, strictly increasing, unique")
        if bool(row.interpolation_used):
            errors.append(f"{row.galaxy_id}: interpolation_used is true")
        if int(row.observed_radial_points) != int(profile_counts.loc[row.galaxy_id]):
            errors.append(f"{row.galaxy_id}: observed point count mismatch")
        if values_hash(csv_values) != row.values_sha256:
            errors.append(f"{row.galaxy_id}: values hash mismatch")

    if int(manifest["is_primary"].fillna(False).astype(bool).sum()) != 121:
        errors.append("primary ladder count is not 121")
    if int(manifest["is_resolution_sensitivity"].fillna(False).astype(bool).sum()) != 75:
        errors.append("resolution-sensitivity ladder count is not 75")

    if errors:
        print("LADDER VALIDATION FAILED", file=sys.stderr)
        print("\n".join(errors), file=sys.stderr)
        return 2
    print("PASS: 175 paired ladders reproduce observed SPARC g_bar samples exactly; no interpolation")
    print("PASS: 121 PRIMARY and 75 RESOLUTION_SENSITIVITY ladders identified")
    return 0


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