#!/usr/bin/env bash
# Validador de integridad del sistema swl-software-engineering-system
# Ejecutar desde la raíz del repositorio:
#   bash habilidades/validacion-ci-sistema/scripts/validar-sistema.sh
#
# Opciones:
#   --solo-errores   Suprime las líneas OK, muestra solo fallos y advertencias
#   --no-color       Desactiva colores (útil en CI sin TTY)
#
# Exit code: 0 si todo pasa, 1 si hay fallos críticos

set -euo pipefail

# ─── Colores ───────────────────────────────────────────────────────────────────
if [[ "${NO_COLOR:-}" == "1" ]] || [[ " $* " == *" --no-color "* ]]; then
  C_OK=""  C_FAIL=""  C_WARN=""  C_BOLD=""  C_RESET=""
else
  C_OK="\033[0;32m"
  C_FAIL="\033[0;31m"
  C_WARN="\033[0;33m"
  C_BOLD="\033[1m"
  C_RESET="\033[0m"
fi

SOLO_ERRORES=0
[[ " $* " == *" --solo-errores "* ]] && SOLO_ERRORES=1

# ─── Contadores globales ────────────────────────────────────────────────────────
TOTAL_ERRORES=0
TOTAL_ADVERTENCIAS=0
TOTAL_OK=0

# ─── Helpers ────────────────────────────────────────────────────────────────────
ok()   { TOTAL_OK=$((TOTAL_OK+1));          [[ $SOLO_ERRORES -eq 0 ]] && printf "  ${C_OK}OK${C_RESET}   %s\n" "$1"; }
fail() { TOTAL_ERRORES=$((TOTAL_ERRORES+1));  printf "  ${C_FAIL}FAIL${C_RESET} %s\n        ERROR: %s\n" "$1" "$2"; }
warn() { TOTAL_ADVERTENCIAS=$((TOTAL_ADVERTENCIAS+1)); printf "  ${C_WARN}WARN${C_RESET} %s\n        WARN: %s\n" "$1" "$2"; }
section() { printf "\n${C_BOLD}%s${C_RESET}\n%s\n" "$1" "$(printf '═%.0s' {1..60})"; }

# ─── Detectar raíz del repositorio ─────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RAIZ="$(cd "${SCRIPT_DIR}/../../.." && pwd)"

if [[ ! -d "${RAIZ}/agentes" ]] || [[ ! -d "${RAIZ}/habilidades" ]]; then
  printf "${C_FAIL}ERROR${C_RESET}: No se encontró la estructura esperada del repositorio SWL.\n"
  printf "       Ejecuta el script desde la raíz: bash habilidades/validacion-ci-sistema/scripts/validar-sistema.sh\n"
  exit 1
fi

printf "${C_BOLD}Validador de integridad — swl-software-engineering-system${C_RESET}\n"
printf "Raíz detectada: %s\n" "${RAIZ}"
printf "Fecha: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')"

# ══════════════════════════════════════════════════════════════════════════════
# 1. AGENTES — Validar frontmatter con campos obligatorios
# ══════════════════════════════════════════════════════════════════════════════
section "1. Agentes (frontmatter)"

CAMPOS_OBLIGATORIOS_AGENTE=("name" "description")
# Campos opcionales pero que deben tener valores válidos si están presentes
MODELOS_VALIDOS=("claude-opus-4-5" "claude-sonnet-4-6" "claude-haiku-3-5")
PERMISOS_VALIDOS=("default" "acceptEdits" "bypassPermissions" "plan")
COLORES_VALIDOS=("red" "orange" "yellow" "green" "blue" "purple" "cyan" "white")

AGENTES_TOTAL=0
while IFS= read -r -d '' archivo; do
  AGENTES_TOTAL=$((AGENTES_TOTAL+1))
  nombre="$(basename "${archivo}" .md)"
  errores_agente=0

  # Extraer frontmatter
  if ! grep -q '^---' "${archivo}"; then
    fail "${nombre}" "No se encontró frontmatter YAML (falta ---)"
    continue
  fi

  frontmatter="$(awk '/^---/{found++; if(found==2) exit} found==1{print}' "${archivo}" | tail -n +1)"

  # Verificar campos obligatorios
  for campo in "${CAMPOS_OBLIGATORIOS_AGENTE[@]}"; do
    valor="$(echo "${frontmatter}" | grep "^${campo}:" | sed 's/^[^:]*: *//' | tr -d '"'"'" | xargs)"
    if [[ -z "${valor}" ]]; then
      fail "${nombre}" "Campo obligatorio ausente o vacío: '${campo}'"
      errores_agente=$((errores_agente+1))
    fi
  done

  # Verificar description mínima (20 chars)
  desc="$(echo "${frontmatter}" | grep "^description:" | sed 's/^description: *//')"
  if [[ ${#desc} -lt 20 ]] && [[ -n "${desc}" ]]; then
    warn "${nombre}" "description demasiado corta (${#desc} chars, mínimo 20)"
  fi

  # Verificar model si está presente
  model="$(echo "${frontmatter}" | grep "^model:" | sed 's/^model: *//' | tr -d '"'"'" | xargs)"
  if [[ -n "${model}" ]]; then
    valido=0
    for m in "${MODELOS_VALIDOS[@]}"; do [[ "${model}" == "${m}" ]] && valido=1 && break; done
    if [[ $valido -eq 0 ]]; then
      warn "${nombre}" "model '${model}' no está en la lista de modelos conocidos"
    fi
  fi

  # Verificar permissionMode si está presente
  pmode="$(echo "${frontmatter}" | grep "^permissionMode:" | sed 's/^permissionMode: *//' | tr -d '"'"'" | xargs)"
  if [[ -n "${pmode}" ]]; then
    valido=0
    for p in "${PERMISOS_VALIDOS[@]}"; do [[ "${pmode}" == "${p}" ]] && valido=1 && break; done
    if [[ $valido -eq 0 ]]; then
      fail "${nombre}" "permissionMode '${pmode}' no es válido. Permitidos: ${PERMISOS_VALIDOS[*]}"
      errores_agente=$((errores_agente+1))
    fi
  fi

  # Verificar color si está presente
  color="$(echo "${frontmatter}" | grep "^color:" | sed 's/^color: *//' | tr -d '"'"'" | xargs)"
  if [[ -n "${color}" ]]; then
    valido=0
    for c in "${COLORES_VALIDOS[@]}"; do [[ "${color}" == "${c}" ]] && valido=1 && break; done
    if [[ $valido -eq 0 ]]; then
      warn "${nombre}" "color '${color}' no es un color reconocido. Conocidos: ${COLORES_VALIDOS[*]}"
    fi
  fi

  [[ $errores_agente -eq 0 ]] && ok "${nombre}"
done < <(find "${RAIZ}/agentes" -name "*.md" -print0 2>/dev/null)

printf "  Total agentes encontrados: %d\n" "${AGENTES_TOTAL}"

# ══════════════════════════════════════════════════════════════════════════════
# 2. HABILIDADES — SKILL.md presente y no vacío
# ══════════════════════════════════════════════════════════════════════════════
section "2. Habilidades (SKILL.md)"

SKILLS_TOTAL=0
KEBAB_REGEX='^[a-z][a-z0-9-]*[a-z0-9]$'

while IFS= read -r -d '' dir; do
  SKILLS_TOTAL=$((SKILLS_TOTAL+1))
  nombre="$(basename "${dir}")"
  errores_skill=0

  # Regla: nombre en kebab-case, máximo 64 chars
  if ! echo "${nombre}" | grep -qE "${KEBAB_REGEX}"; then
    fail "${nombre}" "Nombre no está en kebab-case: '${nombre}'"
    errores_skill=$((errores_skill+1))
  fi
  if [[ ${#nombre} -gt 64 ]]; then
    fail "${nombre}" "Nombre demasiado largo (${#nombre} chars, máximo 64)"
    errores_skill=$((errores_skill+1))
  fi

  # Regla: SKILL.md debe existir
  skill_md="${dir}/SKILL.md"
  if [[ ! -f "${skill_md}" ]]; then
    fail "${nombre}" "SKILL.md no encontrado en el directorio"
    continue
  fi

  # Regla: SKILL.md no debe estar vacío
  contenido="$(cat "${skill_md}" | tr -d '[:space:]')"
  if [[ -z "${contenido}" ]]; then
    fail "${nombre}" "SKILL.md está vacío"
    errores_skill=$((errores_skill+1))
    continue
  fi

  # Regla: debe tener frontmatter con name y description
  if ! head -1 "${skill_md}" | grep -q '^---'; then
    fail "${nombre}" "SKILL.md sin frontmatter YAML"
    errores_skill=$((errores_skill+1))
  else
    frontmatter_skill="$(awk '/^---/{found++; if(found==2) exit} found==1{print}' "${skill_md}")"
    for campo in "name" "description"; do
      valor="$(echo "${frontmatter_skill}" | grep "^${campo}:" | sed 's/^[^:]*: *//' | tr -d '"'"'" | xargs)"
      if [[ -z "${valor}" ]]; then
        fail "${nombre}" "Falta campo '${campo}' en frontmatter de SKILL.md"
        errores_skill=$((errores_skill+1))
      fi
    done

    # Advertencia: SKILL.md muy corto
    lineas="$(wc -l < "${skill_md}")"
    if [[ "${lineas}" -lt 50 ]]; then
      warn "${nombre}" "SKILL.md demasiado corto (${lineas} líneas, mínimo recomendado 50)"
    fi
  fi

  [[ $errores_skill -eq 0 ]] && ok "${nombre}"
done < <(find "${RAIZ}/habilidades" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)

# También verificar skills/ si existe
if [[ -d "${RAIZ}/skills" ]]; then
  while IFS= read -r -d '' dir; do
    SKILLS_TOTAL=$((SKILLS_TOTAL+1))
    nombre="$(basename "${dir}")"
    skill_md="${dir}/SKILL.md"
    [[ -f "${skill_md}" ]] && ok "skills/${nombre}" || fail "skills/${nombre}" "SKILL.md no encontrado"
  done < <(find "${RAIZ}/skills" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
fi

printf "  Total habilidades encontradas: %d\n" "${SKILLS_TOTAL}"

# ══════════════════════════════════════════════════════════════════════════════
# 3. HOOKS — Sintaxis Node.js válida
# ══════════════════════════════════════════════════════════════════════════════
section "3. Hooks (sintaxis Node.js)"

HOOKS_TOTAL=0

if [[ ! -d "${RAIZ}/hooks" ]]; then
  printf "  (directorio hooks/ no encontrado — omitiendo)\n"
else
  while IFS= read -r -d '' hook; do
    HOOKS_TOTAL=$((HOOKS_TOTAL+1))
    nombre="$(basename "${hook}")"
    errores_hook=0

    # Regla 1: Sintaxis Node.js válida
    if ! node --check "${hook}" 2>/dev/null; then
      error_msg="$(node --check "${hook}" 2>&1 | head -1)"
      fail "${nombre}" "Sintaxis Node.js inválida: ${error_msg}"
      errores_hook=$((errores_hook+1))
    fi

    # Regla 2: Debe tener process.exit()
    if ! grep -q 'process\.exit(' "${hook}"; then
      warn "${nombre}" "No tiene process.exit() explícito — puede quedar colgado"
    fi

    # Regla 3: Hooks de observación no deben usar exit(1)
    if echo "${nombre}" | grep -qi 'observe' && grep -q 'process\.exit(1)' "${hook}"; then
      fail "${nombre}" "Hook de observación usa exit(1) — bloquearía el flujo del agente"
      errores_hook=$((errores_hook+1))
    fi

    # Regla 4: execSync sin timeout es peligroso
    if grep -q 'execSync' "${hook}" && ! grep -q 'timeout' "${hook}"; then
      warn "${nombre}" "execSync sin timeout — puede bloquear indefinidamente"
    fi

    [[ $errores_hook -eq 0 ]] && ok "${nombre}"
  done < <(find "${RAIZ}/hooks" -name "*.js" -print0 2>/dev/null)
  printf "  Total hooks encontrados: %d\n" "${HOOKS_TOTAL}"
fi

# ══════════════════════════════════════════════════════════════════════════════
# 4. NOMBRES DE SKILLS — Estándar kebab-case, ≤64 chars
# ══════════════════════════════════════════════════════════════════════════════
# (ya incluido en la sección 2, pero aquí verificamos también agents/ y skills/)
section "4. Nombres de agentes (kebab-case)"

NOMBRES_TOTAL=0
while IFS= read -r -d '' archivo; do
  NOMBRES_TOTAL=$((NOMBRES_TOTAL+1))
  nombre="$(basename "${archivo}" .md)"

  if ! echo "${nombre}" | grep -qE "${KEBAB_REGEX}"; then
    fail "${nombre}" "Nombre de agente no está en kebab-case: '${nombre}'"
  elif [[ ${#nombre} -gt 64 ]]; then
    fail "${nombre}" "Nombre de agente demasiado largo (${#nombre} chars, máximo 64)"
  else
    ok "${nombre}"
  fi
done < <(find "${RAIZ}/agentes" -name "*.md" -print0 2>/dev/null)
printf "  Total nombres verificados: %d\n" "${NOMBRES_TOTAL}"

# ══════════════════════════════════════════════════════════════════════════════
# 5. RESUMEN FINAL
# ══════════════════════════════════════════════════════════════════════════════
printf "\n${C_BOLD}%s${C_RESET}\n" "$(printf '═%.0s' {1..60})"
printf "${C_BOLD}RESUMEN DE VALIDACIÓN${C_RESET}\n"
printf "  OK            : ${C_OK}%d${C_RESET}\n" "${TOTAL_OK}"
printf "  Advertencias  : ${C_WARN}%d${C_RESET}\n" "${TOTAL_ADVERTENCIAS}"
printf "  Errores       : ${C_FAIL}%d${C_RESET}\n" "${TOTAL_ERRORES}"

if [[ ${TOTAL_ERRORES} -gt 0 ]]; then
  printf "\n${C_FAIL}RESULTADO: FAIL${C_RESET} — %d error(es) crítico(s) encontrado(s)\n" "${TOTAL_ERRORES}"
  exit 1
else
  printf "\n${C_OK}RESULTADO: PASS${C_RESET} — Sistema íntegro"
  if [[ ${TOTAL_ADVERTENCIAS} -gt 0 ]]; then
    printf " (${C_WARN}%d advertencia(s) no crítica(s)${C_RESET})" "${TOTAL_ADVERTENCIAS}"
  fi
  printf "\n"
  exit 0
fi
