#!/usr/bin/env bash
# Barrido de compatibilidad componente <-> core real (change verify-core-compat).
# Monta un proyecto temporal por paquete contra el core real publicado, lo
# ejercita segun su nivel de evidencia (tests/compat/levels.json) con criterios
# estrictos (cualquier Deprecated/Warning/Notice/Fatal cuenta FAIL) y escribe
# tests/compat/report.json.
#
# Uso: scripts/compat-sweep [--only <paquete> ...]
# Env:  DUCKBRAIN_CORE_URL  repositorio del core (default: Config::coreUrl())
#       DUCKBRAIN_CACHE_DIR caché donde CoreProvider mantiene el clon del core
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPAT="$ROOT/tests/compat"
LEVELS="$COMPAT/levels.json"
REPORT="$COMPAT/report.json"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

ONLY=()
args=("$@")
i=0
while [ "$i" -lt "${#args[@]}" ]; do
  case "${args[i]}" in
    --only) i=$((i + 1)); ONLY+=("${args[i]}") ;;
    *) echo "Uso: scripts/compat-sweep [--only <paquete> ...]" >&2; exit 2 ;;
  esac
  i=$((i + 1))
done

# --- resolver el core real (clon + checkout del mayor tag semver) --------------
CORE_INFO="$(php -d error_reporting=-1 -r '
require "'"$ROOT"'/src/autoload.php";
try {
    $p = Toolbox\CoreProvider::fromUrl(
        Toolbox\Config::coreUrl(), Toolbox\Config::cacheDir(), new Toolbox\Console()
    );
    $v = $p->version();
    if ($v === null) {
        fwrite(STDERR, "El core no tiene tags semver publicados; nada que verificar.\n");
        exit(1);
    }
    echo $p->dir(), "\n", $v, "\n", (string) $p->commit(), "\n";
} catch (Throwable $e) {
    fwrite(STDERR, "No se pudo acceder al core real: " . $e->getMessage() . "\n");
    exit(1);
}' 2>"$WORK/core.err")" || {
  echo "== ERROR: barrido abortado ==" >&2
  cat "$WORK/core.err" >&2
  exit 1
}
CORE_DIR="$(printf '%s\n' "$CORE_INFO" | sed -n 1p)"
CORE_VER="$(printf '%s\n' "$CORE_INFO" | sed -n 2p)"
CORE_SHA="$(printf '%s\n' "$CORE_INFO" | sed -n 3p)"
echo "== core real: v$CORE_VER ($CORE_SHA) en $CORE_DIR =="
echo

LEVELS_PATH="$LEVELS" php -r '
$levels = json_decode((string) file_get_contents(getenv("LEVELS_PATH")), true);
echo implode("\n", array_keys($levels)), "\n";' > "$WORK/pkgs"

RESULTS="$WORK/results"
: > "$RESULTS"
FAILED=0

wanted() {
  [ "${#ONLY[@]}" -eq 0 ] && return 0
  local w
  for w in "${ONLY[@]}"; do [ "$w" = "$1" ] && return 0; done
  return 1
}

stop_server() {
  if [ -n "${SERVER_PID:-}" ]; then
    kill "$SERVER_PID" 2>/dev/null || true
    wait "$SERVER_PID" 2>/dev/null || true
    SERVER_PID=""
  fi
}

# --- por paquete ---------------------------------------------------------------
while read -r PKG; do
  wanted "$PKG" || continue
  PROJ="$WORK/proj-$PKG"
  LOG="$WORK/$PKG.server.log"
  echo "── $PKG"

  php -d error_reporting=-1 "$ROOT/bin/duckbrain" install "$PROJ" --core-dir="$CORE_DIR" --yes >/dev/null
  php -d error_reporting=-1 "$ROOT/bin/duckbrain" add "$PKG" --root="$PROJ" --yes >/dev/null \
    || { echo "$PKG:add|fail" >> "$RESULTS"; echo "   add fallo"; FAILED=1; continue; }

  # Config del proyecto temporal: sqlite, clave para crypto y db local.
  PROJ_PATH="$PROJ" php -r '
  $proj = getenv("PROJ_PATH");
  $f    = "$proj/config.php";
  $cfg  = (string) file_get_contents($f);
  $cfg  = str_replace("define(\x27DB_TYPE\x27, \x27mysql\x27);", "define(\x27DB_TYPE\x27, \x27sqlite\x27);", $cfg);
  $cfg  = str_replace("define(\x27DB_NAME\x27, \x27\x27);", "define(\x27DB_NAME\x27, \x27$proj/app.sqlite\x27);", $cfg);
  if (!str_contains($cfg, "PRIVATE_KEY")) {
      $cfg .= "\ndefine(\x27PRIVATE_KEY\x27, \x27compat-sweep-clave-de-prueba-32!\x27);\n";
  }
  file_put_contents($f, $cfg);'

  # Router de compatibilidad del fixture (si lo hay).
  if [ -f "$COMPAT/$PKG/router.php" ]; then
    mkdir -p "$PROJ/src/Routers"
    sed "s|@COMPAT@|$COMPAT/$PKG|g" "$COMPAT/$PKG/router.php" > "$PROJ/src/Routers/Compat.php"
  fi

  # Servidor con diagnósticos al máximo y capturados.
  BASE=""
  : > "$LOG"
  for PORT in $(seq 18100 18120); do
    PHP_CLI_SERVER_WORKERS=4 php -d error_reporting=-1 -d display_errors=1 \
      -d log_errors=1 -d error_log="$LOG" -S "127.0.0.1:$PORT" -t "$PROJ" "$PROJ/index.php" \
      >/dev/null 2>&1 &
    SERVER_PID=$!
    for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
      sleep 0.15
      if curl -s -o /dev/null "http://127.0.0.1:$PORT/compat/ping"; then
        BASE="http://127.0.0.1:$PORT"
        break 2
      fi
    done
    stop_server
  done
  if [ -z "$BASE" ]; then
    echo "   no hubo servidor en 18100-18120" >&2
    echo "$PKG:server|fail" >> "$RESULTS"; FAILED=1; continue
  fi

  # Aserciones del fixture (si lo hay; los niveles "none" no tienen).
  if [ -f "$COMPAT/$PKG/assert.sh" ]; then
    BASE="$BASE" PROJ="$PROJ" PKG="$PKG" LOG="$LOG" RESULTS="$RESULTS" COMPAT="$COMPAT" \
      bash "$COMPAT/$PKG/assert.sh" || true
  fi
  stop_server

  # Criterio estricto: cualquier diagnostico en el log del servidor es fallo.
  if grep -qE 'Deprecated|Warning:|Notice:|Fatal error|Parse error' "$LOG"; then
    echo "   diagnostico PHP detectado en el servidor (ver $LOG):"
    grep -E 'Deprecated|Warning:|Notice:|Fatal error|Parse error' "$LOG" | sed 's/^/     /'
    FAILED=1
  fi
done < "$WORK/pkgs"

# --- merge de resultados con levels.json y reporte ------------------------------
LEVELS_PATH="$LEVELS" RESULTS_PATH="$RESULTS" REPORT_PATH="$REPORT" \
CORE_VER="$CORE_VER" CORE_SHA="$CORE_SHA" php -r '
$levels  = json_decode((string) file_get_contents(getenv("LEVELS_PATH")), true);
$results = [];
foreach (file(getenv("RESULTS_PATH"), FILE_IGNORE_NEW_LINES) as $line) {
    [$id, $status] = array_pad(explode("|", $line, 2), 2, "");
    $results[$id] = $status;
}
// Corre parciales (--only): hereda del reporte previo los ok del mismo core.
$prior = is_file(getenv("REPORT_PATH"))
    ? (json_decode((string) file_get_contents(getenv("REPORT_PATH")), true) ?: [])
    : [];
$stale = ($prior["core"]["version"] ?? null) !== getenv("CORE_VER");
$out   = [];
$fail  = 0;
foreach ($levels as $pkg => $comps) {
    foreach ($comps as $comp => $level) {
        $reason = null;
        if (is_array($level)) { $reason = $level["reason"] ?? null; $level = $level["level"]; }
        $id     = "$pkg:$comp";
        $status = $results[$id] ?? ($level === "none" ? "skipped" : "not_attempted");
        if ($status === "not_attempted" && !$stale && ($prior["components"][$id]["status"] ?? "") === "ok") {
            $status = "ok";
        }
        if (!in_array($status, ["ok", "skipped"], true)) { $fail++; }
        $out[$id] = array_filter([
            "status" => $status,
            "level"  => $level,
            "reason" => $reason,
        ], fn ($v) => $v !== null);
    }
}
$report = [
    "core"      => ["version" => getenv("CORE_VER"), "commit" => getenv("CORE_SHA")],
    "generated" => gmdate("Y-m-d\TH:i:s\Z"),
    "components" => $out,
];
file_put_contents(getenv("REPORT_PATH"), json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
foreach ($out as $id => $r) {
    printf("  %-24s %-13s %s%s\n", $id, $r["status"], $r["level"], isset($r["reason"]) ? " (" . $r["reason"] . ")" : "");
}
exit($fail === 0 ? 0 : 1);'

STATUS=$?
if [ "$STATUS" = 0 ]; then
  echo "OK: barrido de compatibilidad completo contra core v$CORE_VER"
else
  echo "FAIL: hay componentes sin verificar o con fallos (ver $REPORT)" >&2
fi
exit $STATUS
