"""Indexar el conocimiento de la propia maquina: comandos, servidores, extensiones.

El RAG de COFRE (indexa.py) sabe de las herramientas del usuario. Esto añade lo
otro que pidio: que sepa hacer CUALQUIER cosa en GNU/Linux. La fuente ya esta en
el disco —9.541 man pages, 3.000 comandos— y es la mejor que hay: la
documentacion oficial de cada herramienta, escrita por quien la hizo.

## Que se extrae de cada man page, y por que no entera

Una man page entera son cientos de lineas: todas las opciones, los ejemplos, las
notas, los bugs. Meterla entera hace el indice enorme y recupera peor —hay tanto
texto que la parte relevante se diluye—. Se coge lo que contesta "¿que hace y
como se llama?":

  NAME       una linea: que es
  SYNOPSIS   como se invoca, con sus opciones
  DESCRIPTION  los primeros parrafos, donde esta lo esencial

Con eso, preguntar "como listo los puertos abiertos" recupera la man de ss o
netstat con su sintaxis, y el cerebro construye el comando desde ahi.

## Solo los comandos que EXISTEN en esta maquina

No las 9.541 man pages: muchas son de librerias de C, formatos de fichero y cosas
que no se invocan desde la terminal. Se indexan las de los comandos que estan de
verdad en el PATH (secciones 1 y 8), que es lo que el usuario puede pedir que se
ejecute.
"""
import os
import re
import subprocess


def _limpio(texto: str) -> str:
    # las man pages traen backspaces para negrita (c\bco\bom\bmo); se quitan
    texto = re.sub(r".\x08", "", texto)
    texto = re.sub(r"\x1b\[[0-9;]*m", "", texto)     # colores ANSI
    return re.sub(r"[ \t]+", " ", texto)


def _comandos_del_path() -> list:
    vistos = set()
    for carpeta in os.environ.get("PATH", "").split(":"):
        try:
            for nombre in os.listdir(carpeta):
                ruta = os.path.join(carpeta, nombre)
                if nombre not in vistos and os.access(ruta, os.X_OK) \
                        and not os.path.isdir(ruta):
                    vistos.add(nombre)
        except OSError:
            continue
    return sorted(vistos)


def _man(comando: str) -> str:
    """NAME + SYNOPSIS + arranque de DESCRIPTION de un comando, o ''."""
    try:
        r = subprocess.run(["man", comando], capture_output=True, text=True,
                           timeout=8, env={**os.environ, "MANWIDTH": "80",
                                           "MAN_KEEP_FORMATTING": ""})
    except (OSError, subprocess.TimeoutExpired):
        return ""
    if r.returncode != 0 or not r.stdout:
        return ""
    texto = _limpio(r.stdout)

    trozos = []
    seccion = None
    buffer = []
    # se cortan las secciones por sus titulos en mayuscula al margen izquierdo
    for linea in texto.splitlines():
        cabecera = re.match(r"^([A-Z][A-Z ]{2,20})$", linea.strip())
        if cabecera:
            if seccion in ("NAME", "SYNOPSIS", "DESCRIPTION") and buffer:
                trozos.append((seccion, "\n".join(buffer).strip()))
            seccion = cabecera.group(1).strip()
            buffer = []
            if seccion in ("OPTIONS", "SEE ALSO", "AUTHOR", "COPYRIGHT",
                           "REPORTING BUGS", "EXAMPLES"):
                seccion = None       # dejar de acumular
        elif seccion:
            buffer.append(linea)
    if seccion in ("NAME", "SYNOPSIS", "DESCRIPTION") and buffer:
        trozos.append((seccion, "\n".join(buffer).strip()))

    partes = []
    for sec, cont in trozos:
        if sec == "DESCRIPTION":
            cont = cont[:600]        # solo el arranque de la descripcion
        partes.append(cont)
    return "\n".join(partes).strip()


def comandos():
    """Una entrada por comando del PATH que tenga man page."""
    for cmd in _comandos_del_path():
        texto = _man(cmd)
        if len(texto) < 40:
            continue
        yield {
            "tipo": "comando", "herramienta": cmd, "perfil": "LINUX",
            "fichero": f"man {cmd}", "texto": texto[:1400],
        }


def servidores():
    """Una ficha por host del ssh config, para que sepa a que puede conectarse."""
    ruta = os.path.expanduser("~/.ssh/config")
    if not os.path.exists(ruta):
        return
    host = None
    datos = {}

    def suelta():
        if host and "*" not in host:
            hn = datos.get("hostname", host)
            usuario = datos.get("user", "")
            desc = (f"Servidor SSH «{host}»: se conecta con «ssh {host}». "
                    f"Direccion {hn}." + (f" Usuario {usuario}." if usuario else "")
                    + " Para ejecutar algo alli: ssh " + host + " '<comando>'.")
            return {"tipo": "servidor", "herramienta": host, "perfil": "SSH",
                    "fichero": "~/.ssh/config", "texto": desc}
        return None

    for linea in open(ruta, encoding="utf-8", errors="ignore"):
        m = re.match(r"^\s*(\w+)\s+(.+?)\s*$", linea)
        if not m:
            continue
        clave, valor = m.group(1).lower(), m.group(2)
        if clave == "host":
            hecho = suelta()
            if hecho:
                yield hecho
            host, datos = valor.split()[0], {}
        elif host:
            datos[clave] = valor
    hecho = suelta()
    if hecho:
        yield hecho


def extensiones_firefox():
    """Que extensiones tiene puestas, para que sepa con que cuenta en el navegador.

    Aviso honesto de alcance: JARVIS puede ABRIR firefox y llevarlo a una URL,
    pero NO puede pulsar los botones de una extension —eso vive dentro del
    navegador, fuera del alcance de un comando de shell—. Lo que si puede es
    saber que tienes puestas y abrir lo que cada una gestiona por URL.
    """
    import glob
    import json as _json
    base = os.path.expanduser("~/.mozilla/firefox")
    perfiles = glob.glob(os.path.join(base, "*", "extensions.json"))
    nombres = []
    for p in perfiles:
        try:
            datos = _json.load(open(p, encoding="utf-8"))
        except (OSError, ValueError):
            continue
        for addon in datos.get("addons", []):
            if not addon.get("active") or addon.get("type") != "extension":
                continue
            nombre = ((addon.get("defaultLocale") or {}).get("name")
                      or addon.get("id", ""))
            if nombre and nombre not in nombres:
                nombres.append(nombre)
    if nombres:
        yield {
            "tipo": "navegador", "herramienta": "firefox", "perfil": "NAVEGADOR",
            "fichero": "extensiones de firefox",
            "texto": ("Extensiones instaladas en Firefox: " + ", ".join(nombres)
                      + ". JARVIS puede abrir Firefox y llevarlo a una URL; los "
                      "botones de cada extension se pulsan dentro del navegador."),
        }


def todo():
    """Todas las entradas de sistema, para que indexa.py las sume a las de COFRE."""
    from saber import glosario
    yield from glosario.entradas()      # el glosario en castellano, primero
    yield from servidores()
    yield from extensiones_firefox()
    yield from comandos()
