#!/usr/bin/env python3
"""Reconstruye la matriz de respuestas (envíos x ítems) de SWE-bench Verified.

Fuente: clon del repositorio público https://github.com/SWE-bench/experiments
(basta un clon parcial; sólo se leen metadata.yaml, results/results.json y
per_instance_details.json de cada carpeta en evaluation/verified/).

Uso:
    git clone --filter=blob:none --no-checkout --depth 1 \
        https://github.com/SWE-bench/experiments
    cd experiments
    git sparse-checkout set --no-cone \
        'evaluation/verified/*/results/results.json' \
        'evaluation/verified/*/per_instance_details.json' \
        'evaluation/verified/*/metadata.yaml'
    git checkout
    python3 preparar_datos.py /ruta/a/experiments [carpeta_salida]

Salidas (en carpeta_salida, por defecto la carpeta de este script):
    respuestas_swebench_verified.csv  1 fila por envío incluido; 500 columnas 0/1
    envios_swebench_verified.csv      metadatos de TODOS los envíos y motivo de exclusión

Codificación: 1 = el instance_id aparece como resuelto; 0 = cualquier otro caso
(no resuelto, sin parche, error de evaluación). Es la misma regla con la que el
leaderboard calcula "% resolved" sobre un denominador fijo de 500.

Requiere: Python >= 3.9, PyYAML.
"""
import csv
import json
import subprocess
import sys
from pathlib import Path

import yaml

TOLERANCIA_PP = 1.0  # discrepancia máxima (puntos %) con el "resolved" declarado


def leer_resueltos(carpeta: Path):
    """Devuelve (ids resueltos, ids observados, fuente, n sin parche generado)."""
    res = carpeta / "results" / "results.json"
    pid = carpeta / "per_instance_details.json"
    if res.exists():
        r = json.loads(res.read_text())
        vistos = set()
        for v in r.values():
            if isinstance(v, list):
                vistos |= set(v)
        sin_parche = len(r["no_generation"]) if "no_generation" in r else None
        return set(r["resolved"]), vistos, "results.json", sin_parche
    if pid.exists():
        d = json.loads(pid.read_text())
        # ids ausentes del archivo = instancias sin parche registrado
        return ({k for k, v in d.items() if v.get("resolved")}, set(d),
                "per_instance_details.json", 500 - len(d))
    return None, None, None, None


def main():
    if len(sys.argv) < 2:
        sys.exit(__doc__)
    raiz = Path(sys.argv[1])
    salida = Path(sys.argv[2]) if len(sys.argv) > 2 else Path(__file__).parent
    base = raiz / "evaluation" / "verified"
    carpetas = sorted(p for p in base.iterdir() if p.is_dir())

    try:
        commit = subprocess.run(["git", "-C", str(raiz), "rev-parse", "HEAD"],
                                capture_output=True, text=True).stdout.strip()
    except OSError:
        commit = ""

    # Universo de ítems: unión de ids observados en archivos per_instance_details
    # completos (500 claves) y en results.json.
    universo = set()
    datos = {}
    for c in carpetas:
        datos[c.name] = leer_resueltos(c)
        resueltos, vistos, fuente, _ = datos[c.name]
        if vistos is not None and fuente == "per_instance_details.json" and len(vistos) == 500:
            universo |= vistos
    items = sorted(universo)
    assert len(items) == 500, f"Se esperaban 500 instance_ids, hay {len(items)}"

    filas_meta, filas_resp = [], []
    for c in carpetas:
        resueltos, vistos, fuente, sin_parche = datos[c.name]
        meta_path = c / "metadata.yaml"
        m = yaml.safe_load(meta_path.read_text()) if meta_path.exists() else {}
        info, tags = m.get("info") or {}, m.get("tags") or {}
        sistema = tags.get("system") or {}
        declarado = info.get("resolved")

        motivo = ""
        n_res = None
        if resueltos is None:
            motivo = "sin datos por ítem"
        else:
            fuera = resueltos - universo
            assert not fuera, f"{c.name}: ids desconocidos {list(fuera)[:3]}"
            n_res = len(resueltos)
            if declarado is not None and abs(100 * n_res / 500 - float(declarado)) > TOLERANCIA_PP:
                motivo = (f"inconsistente: {n_res}/500 por ítem vs "
                          f"{declarado}% declarado")

        filas_meta.append({
            "envio": c.name,
            "fecha": f"{c.name[:4]}-{c.name[4:6]}-{c.name[6:8]}",
            "nombre": info.get("name", c.name[9:]),
            "agente": tags.get("agent", ""),
            "modelo": tags.get("model_display", ""),
            "org_modelo": tags.get("model_org", tags.get("org", "")),
            "intentos": sistema.get("attempts", ""),
            "checked": int(str(tags.get("checked", "")).strip().lower().startswith("true")),
            "modelo_abierto": tags.get("os_model", ""),
            "sistema_abierto": tags.get("os_system", ""),
            "mini_swe_agent": int(tags.get("agent") == "mini-SWE-agent"),
            "fuente": fuente or "",
            "n_resueltos": "" if n_res is None else n_res,
            "n_sin_parche": "" if sin_parche is None else sin_parche,
            "pct_declarado": "" if declarado is None else declarado,
            "incluido": int(motivo == ""),
            "motivo_exclusion": motivo,
        })
        if motivo == "":
            filas_resp.append([c.name] + [int(i in resueltos) for i in items])

    salida.mkdir(parents=True, exist_ok=True)
    with open(salida / "envios_swebench_verified.csv", "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=list(filas_meta[0]))
        w.writeheader()
        w.writerows(filas_meta)
    with open(salida / "respuestas_swebench_verified.csv", "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["envio"] + items)
        w.writerows(filas_resp)

    print(f"commit de SWE-bench/experiments: {commit or 'desconocido'}")
    print(f"carpetas: {len(carpetas)} | incluidas: {len(filas_resp)} | ítems: {len(items)}")
    for fm in filas_meta:
        if not fm["incluido"]:
            print(f"  excluido {fm['envio']}: {fm['motivo_exclusion']}")


if __name__ == "__main__":
    main()
