"""Read-only, bounded, redacted engineering tools for Hermes. Python 3.8."""
import ast
import re
from pathlib import Path
ROOT = Path('/var/www/html/NFL_ST')
DOC = Path('/var/lib/hermes-nexus/knowledge/LMP_V5.md')
ALLOW = frozenset(('nfl.py','panel.html','hermes_api.py','hermes_brain.py',
 'hermes_brain_v2.py','hermes_memory.py','hermes_tools.py','hermes_football.py',
 'hermes_lmp_v5_runtime.py','hermes_lmp_coordinator.py','hermes_lmp_actions.py',
 'hermes_epg_tool.py','hermes_epg_search.py','hermes_web.py',
 'hermes_lmp_epg_cache_v2.py','hermes_lmp_v5_state.py'))
SENSITIVE = re.compile(r'(?i)(password|passwd|secret|token|api.?key|authorization|credential|cookie|bearer|basic\s|refresh|session.?id|private.?key|client.?data|auth\b|\bp=|\bu=)')
URL_CREDENTIAL = re.compile(r'https?://\S+',re.I)

def _safe_line(s):
    # Do not expose lines containing credentials, including URLs with signed parameters.
    if SENSITIVE.search(s): return '[REDACTADO: posible secreto o autenticación]'
    if URL_CREDENTIAL.search(s) and ('?' in s or '@' in s): return '[REDACTADO: URL potencialmente sensible]'
    return s[:320]

def _path(name):
    if not isinstance(name,str) or name not in ALLOW: raise ValueError('Archivo no autorizado')
    p=ROOT/name
    if p.is_symlink() or not p.is_file() or p.stat().st_size > 2_000_000: raise ValueError('Archivo no disponible')
    if p.resolve().parent != ROOT.resolve(): raise ValueError('Ruta fuera del proyecto')
    return p

def listar_archivos():
    return {'archivos':[n for n in sorted(ALLOW) if (ROOT/n).is_file() and not (ROOT/n).is_symlink()]}

def leer_archivo(archivo,inicio=1,cantidad=45):
    p=_path(archivo)
    inicio=max(1,min(int(inicio),50000));cantidad=max(1,min(int(cantidad),60))
    with p.open(encoding='utf-8',errors='replace') as f:
        from itertools import islice
        lines=list(islice(f,inicio-1,inicio-1+cantidad))
    return {'archivo':archivo,'inicio':inicio,'lineas':[{'numero':inicio+i,'texto':_safe_line(line.rstrip('\n'))} for i,line in enumerate(lines)]}

def buscar_codigo(termino,archivo=''):
    if not isinstance(termino,str) or not (2<=len(termino)<=70): raise ValueError('Término inválido')
    if re.search(r'(?i)(password|token|secret|api.?key|auth|cookie|credential|bearer)',termino):
        raise ValueError('Búsqueda de credenciales no permitida')
    names=[archivo] if archivo else sorted(ALLOW)
    hits=[]
    for name in names:
        p=_path(name) if archivo else (ROOT/name)
        if not p.is_file() or p.is_symlink() or p.stat().st_size>2_000_000: continue
        with p.open(encoding='utf-8',errors='replace') as f:
            for num,line in enumerate(f,1):
                if termino.casefold() in line.casefold():
                    safe=_safe_line(line.strip())
                    if safe.startswith('[REDACTADO'): continue
                    hits.append({'archivo':name,'linea':num,'texto':safe})
                    if len(hits)>=25:return {'resultados':hits,'limite':25}
    return {'resultados':hits,'limite':25}

def leer_lmp():
    if DOC.is_symlink() or not DOC.is_file():return {'error':'Documento LMP no disponible'}
    return {'documento':'LMP_V5.md','contenido':DOC.read_text(encoding='utf-8')[:14000],
      'aviso':'Documento orientativo: contrastar con el código actual.'}
