"""
validate-skills.py — Validador de habilidades del sistema SWL

Uso:
  python scripts/validate-skills.py                          # valida todas las skills
  python scripts/validate-skills.py --skill django-experto   # una skill específica
  python scripts/validate-skills.py --check                  # modo CI: exit 1 si hay errores
  python scripts/validate-skills.py --format json            # output en formato JSON

Exit code 0 = éxito o solo advertencias; 1 = hay errores.
Directorio de habilidades: ../habilidades (relativo a scripts/).
"""

import argparse
import json
import os
import re
import sys
from pathlib import Path

# ---------------------------------------------------------------------------
# Compatibilidad YAML: intentar pyyaml, luego yaml stdlib (si existe),
# luego parser manual mínimo para frontmatter.
# ---------------------------------------------------------------------------
try:
    import yaml as _yaml

    def parsear_yaml(texto: str) -> dict:
        return _yaml.safe_load(texto) or {}

except ImportError:
    def parsear_yaml(texto: str) -> dict:
        """Parser mínimo de YAML plano para frontmatter (sin pyyaml)."""
        resultado: dict = {}
        lineas = texto.splitlines()
        clave_actual = None
        buffer_multilinea: list[str] = []
        es_bloque = False

        def _guardar_clave(clave: str, valor: str) -> None:
            resultado[clave] = valor.strip().strip('"').strip("'")

        for linea in lineas:
            # Detectar continuación de bloque escalar (description: >)
            if es_bloque:
                if linea.startswith("  ") or linea.strip() == "":
                    buffer_multilinea.append(linea.strip())
                    continue
                else:
                    resultado[clave_actual] = " ".join(
                        b for b in buffer_multilinea if b
                    )
                    buffer_multilinea = []
                    es_bloque = False
                    clave_actual = None

            if ":" in linea:
                partes = linea.split(":", 1)
                clave = partes[0].strip()
                valor = partes[1].strip() if len(partes) > 1 else ""
                if valor in (">", "|", "|-", ">-"):
                    clave_actual = clave
                    es_bloque = True
                    buffer_multilinea = []
                elif clave:
                    _guardar_clave(clave, valor)

        if es_bloque and clave_actual and buffer_multilinea:
            resultado[clave_actual] = " ".join(b for b in buffer_multilinea if b)

        return resultado


# ---------------------------------------------------------------------------
# Constantes
# ---------------------------------------------------------------------------
PALABRAS_RESERVADAS = {"anthropic", "claude", "swl", "test", "tests", "testing"}
REGEX_KEBAB = re.compile(r"^[a-z0-9][a-z0-9-]*$")
REGEX_RECURSO_MD = re.compile(r"recursos/[\w.\-/]+", re.IGNORECASE)
PALABRAS_OVER_CONSTRAINED = re.compile(
    r"\b(MUST|ALWAYS|NEVER|NUNCA|SIEMPRE|OBLIGATORIO)\b"
)
TRIGGERS = re.compile(r"cargar cuando|invocar cuando", re.IGNORECASE)

MAX_NAME_LEN = 64
MAX_DESC_LEN = 1024
MAX_SKILL_LINES = 300
MAX_OVER_CONSTRAINED = 15


# ---------------------------------------------------------------------------
# Tipos de resultado
# ---------------------------------------------------------------------------
def _problema(tipo: str, codigo: str, mensaje: str) -> dict:
    return {"type": tipo, "code": codigo, "message": mensaje}


def _error(codigo: str, mensaje: str) -> dict:
    return _problema("error", codigo, mensaje)


def _advertencia(codigo: str, mensaje: str) -> dict:
    return _problema("warning", codigo, mensaje)


# ---------------------------------------------------------------------------
# Lógica de validación
# ---------------------------------------------------------------------------
def _extraer_frontmatter(contenido: str) -> tuple[str | None, str]:
    """
    Devuelve (texto_frontmatter, cuerpo_sin_frontmatter).
    Si no hay bloque ---, devuelve (None, contenido completo).
    """
    if not contenido.startswith("---"):
        return None, contenido

    fin = contenido.find("\n---", 3)
    if fin == -1:
        return None, contenido

    texto_fm = contenido[3:fin].strip()
    cuerpo = contenido[fin + 4:].lstrip("\n")
    return texto_fm, cuerpo


def validar_skill(ruta_dir: Path) -> dict:
    """
    Valida una skill ubicada en ruta_dir.
    Devuelve dict con claves: skill, status, issues.
    """
    nombre_dir = ruta_dir.name
    issues: list[dict] = []

    ruta_skill_md = ruta_dir / "SKILL.md"
    if not ruta_skill_md.exists():
        issues.append(_error("MISSING_SKILL_MD", "No existe SKILL.md en el directorio"))
        return {"skill": nombre_dir, "status": "error", "issues": issues}

    contenido = ruta_skill_md.read_text(encoding="utf-8", errors="replace")
    lineas = contenido.splitlines()
    num_lineas = len(lineas)

    # --- Frontmatter ---
    texto_fm, cuerpo = _extraer_frontmatter(contenido)

    if texto_fm is None:
        issues.append(_error("INVALID_FRONTMATTER", "No se encontro bloque frontmatter YAML (---...---)"))
        return {"skill": nombre_dir, "status": "error", "issues": issues}

    try:
        fm = parsear_yaml(texto_fm)
    except Exception as exc:
        issues.append(_error("INVALID_FRONTMATTER", f"Frontmatter YAML no parseable: {exc}"))
        return {"skill": nombre_dir, "status": "error", "issues": issues}

    # --- Campo name ---
    nombre = fm.get("name")
    if not nombre:
        issues.append(_error("MISSING_NAME", "Campo 'name' ausente en frontmatter"))
    else:
        nombre = str(nombre).strip()
        if not REGEX_KEBAB.match(nombre):
            issues.append(_error(
                "INVALID_NAME_FORMAT",
                f"'name' no es kebab-case valido: '{nombre}' (regex: ^[a-z0-9][a-z0-9-]*$)"
            ))
        if len(nombre) > MAX_NAME_LEN:
            issues.append(_error(
                "NAME_TOO_LONG",
                f"'name' supera {MAX_NAME_LEN} caracteres ({len(nombre)})"
            ))
        for reservada in PALABRAS_RESERVADAS:
            partes = nombre.split("-")
            if reservada in partes:
                issues.append(_error(
                    "RESERVED_NAME",
                    f"'name' contiene palabra reservada: '{reservada}'"
                ))
        if nombre != nombre_dir:
            issues.append(_error(
                "NAME_MISMATCH",
                f"'name' ('{nombre}') no coincide con el directorio ('{nombre_dir}')"
            ))

    # --- Campo description ---
    descripcion = fm.get("description")
    if not descripcion:
        issues.append(_error("MISSING_DESCRIPTION", "Campo 'description' ausente o vacio en frontmatter"))
    else:
        descripcion = str(descripcion).strip()
        if len(descripcion) > MAX_DESC_LEN:
            issues.append(_error(
                "DESCRIPTION_TOO_LONG",
                f"'description' supera {MAX_DESC_LEN} caracteres ({len(descripcion)})"
            ))
        if not TRIGGERS.search(descripcion):
            issues.append(_error(
                "MISSING_TRIGGER",
                "description no contiene 'Cargar cuando' ni 'Invocar cuando'"
            ))

    # --- Verificar líneas ---
    if num_lineas > MAX_SKILL_LINES:
        issues.append(_advertencia(
            "BLOATED_SKILL",
            f"SKILL.md supera {MAX_SKILL_LINES} lineas ({num_lineas} lineas)"
        ))

    # --- Over-constrained ---
    ocurrencias_oc = len(PALABRAS_OVER_CONSTRAINED.findall(contenido))
    if ocurrencias_oc > MAX_OVER_CONSTRAINED:
        issues.append(_advertencia(
            "OVER_CONSTRAINED",
            f"SKILL.md tiene {ocurrencias_oc} ocurrencias de MUST/ALWAYS/NEVER/NUNCA/SIEMPRE/OBLIGATORIO "
            f"(maximo recomendado: {MAX_OVER_CONSTRAINED})"
        ))

    # --- Description habla de sí misma ---
    if nombre and descripcion:
        if nombre.lower() in descripcion.lower():
            issues.append(_advertencia(
                "SELF_REFERENTIAL_DESC",
                f"La description menciona el propio name ('{nombre}')"
            ))

    # --- Referencias rotas a recursos ---
    referencias_en_md = set(REGEX_RECURSO_MD.findall(contenido))
    for ref in referencias_en_md:
        ruta_ref = ruta_dir / ref
        if not ruta_ref.exists():
            issues.append(_error(
                "BROKEN_RESOURCE_LINK",
                f"Referencia a recurso no encontrado: '{ref}'"
            ))

    # --- Archivos huérfanos en recursos/ ---
    ruta_recursos = ruta_dir / "recursos"
    if ruta_recursos.is_dir():
        archivos_recursos = {
            f.name for f in ruta_recursos.iterdir() if f.is_file()
        }
        # Extraer solo el nombre del archivo de las referencias
        nombres_referenciados = {Path(r).name for r in referencias_en_md}
        for archivo in archivos_recursos:
            if archivo not in nombres_referenciados:
                issues.append(_advertencia(
                    "ORPHAN_RESOURCE",
                    f"Archivo en recursos/ no referenciado desde SKILL.md: '{archivo}'"
                ))

    # --- Determinar estado final ---
    tiene_error = any(i["type"] == "error" for i in issues)
    tiene_advertencia = any(i["type"] == "warning" for i in issues)

    if tiene_error:
        estado = "error"
    elif tiene_advertencia:
        estado = "warning"
    else:
        estado = "ok"

    return {"skill": nombre_dir, "status": estado, "issues": issues}


# ---------------------------------------------------------------------------
# Presentación de resultados
# ---------------------------------------------------------------------------
def _iconos() -> dict[str, str]:
    """Devuelve iconos Unicode si el terminal los soporta, ASCII en caso contrario."""
    enc = getattr(sys.stdout, "encoding", "") or ""
    if enc.lower().replace("-", "") in ("utf8", "utf16", "utf32"):
        return {"ok": "\u2713", "warning": "\u26a0", "error": "\u2717"}
    return {"ok": "[OK]", "warning": "[WARN]", "error": "[ERR]"}


def imprimir_resultado_normal(resultado: dict, iconos: dict[str, str]) -> None:
    skill = resultado["skill"]
    estado = resultado["status"]
    icono = iconos.get(estado, "?")
    issues = resultado["issues"]

    if estado == "ok":
        print(f"{icono} {skill} - OK")
    else:
        for issue in issues:
            prefijo = "ERROR" if issue["type"] == "error" else "ADVERTENCIA"
            print(f"{icono} {skill} - {prefijo}: {issue['message']}")


# ---------------------------------------------------------------------------
# Punto de entrada
# ---------------------------------------------------------------------------
def main() -> int:
    parser = argparse.ArgumentParser(
        description="Validador de habilidades del sistema SWL"
    )
    parser.add_argument(
        "--skill",
        metavar="NOMBRE",
        help="Validar solo una skill específica (nombre del directorio)"
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Modo CI: exit 1 si hay errores"
    )
    parser.add_argument(
        "--format",
        choices=["normal", "json"],
        default="normal",
        help="Formato de salida (default: normal)"
    )
    args = parser.parse_args()

    raiz = Path(__file__).parent.parent
    dir_habilidades = raiz / "habilidades"

    if not dir_habilidades.is_dir():
        print(f"ERROR: Directorio de habilidades no encontrado: {dir_habilidades}", file=sys.stderr)
        return 1

    # Seleccionar skills a validar
    if args.skill:
        ruta_skill = dir_habilidades / args.skill
        if not ruta_skill.is_dir():
            print(f"ERROR: Skill '{args.skill}' no encontrada en {dir_habilidades}", file=sys.stderr)
            return 1
        skills_a_validar = [ruta_skill]
    else:
        skills_a_validar = sorted(
            [p for p in dir_habilidades.iterdir() if p.is_dir()],
            key=lambda p: p.name
        )

    resultados: list[dict] = []
    for ruta in skills_a_validar:
        resultado = validar_skill(ruta)
        resultados.append(resultado)

    # Contadores
    conteo = {"ok": 0, "warnings": 0, "errors": 0}
    for r in resultados:
        if r["status"] == "ok":
            conteo["ok"] += 1
        elif r["status"] == "warning":
            conteo["warnings"] += 1
            conteo["ok"] += 0  # no se suma a OK
        elif r["status"] == "error":
            conteo["errors"] += 1

    # Recalcular ok como "sin errores ni advertencias"
    conteo["ok"] = sum(1 for r in resultados if r["status"] == "ok")

    if args.format == "json":
        salida = {
            "summary": {
                "ok": conteo["ok"],
                "warnings": conteo["warnings"],
                "errors": conteo["errors"]
            },
            "results": resultados
        }
        print(json.dumps(salida, ensure_ascii=False, indent=2))
    else:
        iconos = _iconos()
        for resultado in resultados:
            imprimir_resultado_normal(resultado, iconos)

        total = len(resultados)
        partes_resumen = [f"{total} skill(s) validada(s)"]
        partes_resumen.append(f"{conteo['ok']} OK")
        if conteo["warnings"]:
            partes_resumen.append(f"{conteo['warnings']} advertencia(s)")
        if conteo["errors"]:
            partes_resumen.append(f"{conteo['errors']} error(es)")
        print(f"\nResumen: {', '.join(partes_resumen)}")

    if args.check and conteo["errors"] > 0:
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())
