import os, json, datetime
import numpy as np
import pandas as pd
from .common import sha256_file


def gate_axion(dataset_dir: str) -> dict:
    pred_path = os.path.join(dataset_dir, "axion_pred.csv")
    lim_path = os.path.join(dataset_dir, "axion_limit95.csv")
    band_path = os.path.join(dataset_dir, "axion_prediction_band_v23.json")

    pred = pd.read_csv(pred_path)
    lim = pd.read_csv(lim_path)
    with open(band_path, "r", encoding="utf-8") as f:
        band_record = json.load(f)

    def first_numeric_cols(df):
        cols=[]
        for c in df.columns:
            if pd.api.types.is_numeric_dtype(df[c]):
                cols.append(c)
        return cols

    pcols=first_numeric_cols(pred)
    lcols=first_numeric_cols(lim)
    mcol_p = pcols[0]
    mcol_l = lcols[0]
    gcol_p = pcols[1] if len(pcols)>1 else None
    gcol_l = lcols[1] if len(lcols)>1 else None

    masses = pred[mcol_p].to_numpy(dtype=float)
    comparison_min = float(np.min(masses))
    comparison_max = float(np.max(masses))

    declared = band_record["prediction_band"]
    declared_min = float(declared["m_min_ueV"])
    declared_max = float(declared["m_max_ueV"])

    allowed_mask = np.ones_like(masses, dtype=bool)
    frac_allowed = None
    max_ratio = None
    if gcol_p is not None and gcol_l is not None:
        lim_interp = np.interp(
            masses,
            lim[mcol_l].to_numpy(dtype=float),
            lim[gcol_l].to_numpy(dtype=float),
            left=np.nan,
            right=np.nan,
        )
        ratios = pred[gcol_p].to_numpy(dtype=float) / lim_interp
        known = np.isfinite(lim_interp)
        allowed_mask[known] = ratios[known] < 1.0
        frac_allowed = float(np.mean(allowed_mask[known])) if np.any(known) else None
        max_ratio = float(np.nanmax(ratios)) if np.any(known) else None
        known_count = int(np.sum(known))
    else:
        known_count = 0

    relation = band_record["upstream_relation"]
    fmin = float(relation["f_a_min_GeV"])
    fmax = float(relation["f_a_max_GeV"])
    low_unrounded = 5.7 * (1.0e12 / fmax)
    high_unrounded = 5.7 * (1.0e12 / fmin)

    checks = {
        "declared_prediction_band_is_6_to_60_ueV": declared_min == 6.0 and declared_max == 60.0,
        "fa_relation_recomputes_unrounded_low_endpoint": abs(low_unrounded - float(relation["unrounded_endpoint_from_f_a_max_ueV"])) < 1e-12,
        "fa_relation_recomputes_unrounded_high_endpoint": abs(high_unrounded - float(relation["unrounded_endpoint_from_f_a_min_ueV"])) < 1e-12,
        "comparison_table_is_inside_prediction_band": comparison_min >= declared_min and comparison_max <= declared_max,
        "comparison_table_coverage_matches_record": comparison_min == float(band_record["row21_comparison_coverage"]["m_min_ueV"]) and comparison_max == float(band_record["row21_comparison_coverage"]["m_max_ueV"]),
        "full_prediction_band_directly_tested_by_root_table": comparison_min <= declared_min and comparison_max >= declared_max,
        "missing_6_to_10_data_not_fabricated": band_record["boundary"]["do_not_invent_6_to_10_limit_data"] is True,
    }

    # Row 21 is diagnostic. PASS means the tested comparison table leaves a non-empty
    # allowed region inside the declared 6-60 ueV prediction band. It does not claim
    # direct 95% coverage of the untested 6-10 ueV segment.
    if frac_allowed is None:
        verdict="PENDING_DATA"
    else:
        verdict="PASS" if frac_allowed > 0.0 else "FAIL"

    status = (
        "PASS_AXION_DECLARED_6_60_BAND__ROOT_COMPARISON_COVERS_10_60"
        if verdict == "PASS"
        else ("PENDING_AXION_COMPARISON_DATA" if verdict == "PENDING_DATA" else "FAIL_AXION_TESTED_REGION_EXCLUDED")
    )

    return {
        "row": 21,
        "gate": "axion_dark_matter_gate",
        "context": "LIVE",
        "tier": "Diagnostic",
        "timestamp_utc": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds").replace("+00:00", "Z"),
        "summary": {
            # Canonical public prediction-band fields.
            "m_min_ueV": declared_min,
            "m_max_ueV": declared_max,
            "prediction_band_m_min_ueV": declared_min,
            "prediction_band_m_max_ueV": declared_max,
            # Explicitly separate the actual current comparison-table coverage.
            "comparison_m_min_ueV": comparison_min,
            "comparison_m_max_ueV": comparison_max,
            "comparison_covers_full_prediction_band": bool(checks["full_prediction_band_directly_tested_by_root_table"]),
            "comparison_known_points": known_count,
            "frac_allowed_known": frac_allowed,
            "max_pred_over_limit": max_ratio,
            "unrounded_mass_endpoints_from_fa_ueV": [low_unrounded, high_unrounded],
        },
        "checks": checks,
        "verdict": verdict,
        "status": status,
        "blocking": False,
        "claim_boundary": {
            "declared_prediction_band": "6-60 ueV (published rounded band)",
            "current_root_comparison_coverage": "10-60 ueV",
            "direct_6_to_10_limit_comparison_in_root_table": False,
            "pass_means_full_band_empirically_cleared": False,
            "pass_meaning": band_record["boundary"]["pass_meaning"],
        },
        "inputs": {
            "hashes": {
                "axion_pred.csv": sha256_file(pred_path),
                "axion_limit95.csv": sha256_file(lim_path),
                "axion_prediction_band_v23.json": sha256_file(band_path),
            }
        },
        "notes": (
            "v23 separates the declared 6-60 ueV prediction/search band from the 10-60 ueV "
            "coverage of the current root Row 21 comparison table. No 6-10 ueV limit data are fabricated."
        ),
    }


if __name__ == "__main__":
    here = os.path.dirname(__file__)
    dataset_dir = os.path.abspath(os.path.join(here, "..", "datasets"))
    out_dir = os.path.abspath(os.path.join(here, "..", "results", "row21_axion"))
    os.makedirs(out_dir, exist_ok=True)
    res = gate_axion(dataset_dir)
    with open(os.path.join(out_dir, "result.json"), "w", encoding="utf-8") as f:
        json.dump(res, f, indent=2, sort_keys=True)
    print(json.dumps(res, indent=2, sort_keys=True))
