"""skills/supervisor_esceptico.py — SUPERVISOR genérico (L3 Opus auditor con PDF como base)

Patrón Andon Tier L3: cuando L1 verifier FAIL o L2 olfato Haiku FLAG.
Este supervisor lee el PDF sub-proceso de su estación + la sección de la caja del agente
y decide APPROVE / BREAK_<reason>.

Backend: claude CLI subprocess (Opus 4.7 con PDF inline como contexto base canónica).

Cada station_id tiene su PDF sub-proceso anclado en _pdfs/PDF_<STATION>.pdf.
El supervisor lo carga y lo incluye en su prompt — la skill audita CONTRA el PDF, NO contra reglas hardcoded.

Inputs:
  context.station_id (qué estación auditar)
  caja.<station_id> (output del agente)

Output:
  SkillResult.ok = True si APPROVE, False si BREAK
  SkillResult.errors = [BREAK_<code> · razón concreta]
"""
from __future__ import annotations
import json
from pathlib import Path
from skills._lib import SkillContext, SkillResult

ROOT = Path(__file__).resolve().parents[1]
PDFS_DIR = ROOT / '_pdfs'

PDF_NAME_MAP = {
    'descargar': 'PDF_DESCARGAR.pdf',
    'analizar': 'PDF_ANALIZAR.pdf',
    'interpretar': 'PDF_INTERPRETAR.pdf',
    'buscar': 'PDF_BUSCAR.pdf',
    'comparador': 'PDF_COMPARADOR.pdf',
    'trello_editor': 'PDF_TRELLO_EDITOR.pdf',
}


def run(inputs: dict, context: SkillContext) -> SkillResult:
    """inputs.station_id (puede venir explícito) o context.station_id."""
    target_station = inputs.get('audit_station_id') or context.station_id
    if not target_station:
        return SkillResult(ok=False, errors=['BREAK_NO_TARGET · no se especificó audit_station_id'])

    caja = context.read_caja()
    section = caja.get('sections', {}).get(target_station)
    if section is None:
        return SkillResult(ok=False, errors=[f'BREAK_EMPTY_SECTION · caja.sections.{target_station} no existe'])
    if isinstance(section, dict) and not section:
        return SkillResult(ok=False, errors=[f'BREAK_EMPTY_SECTION · caja.sections.{target_station} es {{}}'])
    if isinstance(section, list) and len(section) == 0:
        return SkillResult(ok=False, errors=[f'BREAK_EMPTY_SECTION · caja.sections.{target_station} es []'])

    # Cargar PDF sub-proceso como base canónica
    pdf_name = PDF_NAME_MAP.get(target_station)
    pdf_path = (PDFS_DIR / pdf_name) if pdf_name else None
    if pdf_path is None or not pdf_path.exists():
        return SkillResult(ok=False, errors=[f'BREAK_PDF_BASE_MISSING · PDF base no existe: {pdf_path}'])

    pdf_text = _extract_pdf_text(pdf_path)

    prompt = build_audit_prompt(target_station, pdf_text, section, caja.get('input_start', {}))
    res = context.claude_cli(prompt, model='claude-opus-4-7', timeout_s=180)
    if not res.get('ok'):
        return SkillResult(ok=False, errors=[f'BREAK_LLM_FAIL · claude CLI falló: {res.get("stderr") or res.get("error")}'])

    verdict_text = (res.get('stdout') or '').strip()
    if 'APPROVE' in verdict_text.upper().split()[:3] if verdict_text else []:
        return SkillResult(ok=True, output={'verdict': 'APPROVE', 'reason': verdict_text[:300]})
    if verdict_text.upper().startswith('APPROVE'):
        return SkillResult(ok=True, output={'verdict': 'APPROVE', 'reason': verdict_text[:300]})
    # Si arranca con BREAK_ → fail con razón
    if verdict_text.upper().startswith('BREAK'):
        return SkillResult(ok=False, errors=[verdict_text[:500]])
    # Fallback: si no coincide formato, asume APPROVE solo si verdict_text contiene APPROVE
    if 'APPROVE' in verdict_text.upper():
        return SkillResult(ok=True, output={'verdict': 'APPROVE_inferred', 'raw': verdict_text[:300]})
    # Default conservador: BREAK
    return SkillResult(ok=False, errors=[f'BREAK_AMBIGUOUS · supervisor no devolvió APPROVE/BREAK claro: {verdict_text[:300]}'])


def _extract_pdf_text(pdf_path: Path, max_chars: int = 6000) -> str:
    """Extrae texto del PDF. Usa pdftotext si disponible, fallback pypdf."""
    import subprocess
    try:
        r = subprocess.run(['pdftotext', str(pdf_path), '-'], capture_output=True, text=True, timeout=15)
        if r.returncode == 0:
            return r.stdout[:max_chars]
    except Exception:
        pass
    try:
        import pypdf
        with open(pdf_path, 'rb') as f:
            reader = pypdf.PdfReader(f)
            text = '\n'.join(p.extract_text() or '' for p in reader.pages)
            return text[:max_chars]
    except Exception:
        return f'[PDF extraction failed for {pdf_path.name}]'


def build_audit_prompt(station_id: str, pdf_text: str, section: dict, input_start: dict) -> str:
    section_str = json.dumps(section, ensure_ascii=False, indent=2)[:4000]
    input_str = json.dumps(input_start, ensure_ascii=False, indent=2)[:1000]
    return f"""Eres el SUPERVISOR L3 (Opus 4.7) del flow Factory v4 escalado de formato.
Auditar el output de la estación "{station_id}" contra su PDF base canónica.

=== PDF BASE SUB-PROCESO {station_id.upper()} (verbatim) ===
{pdf_text}
=== FIN PDF ===

=== INPUT START del flow ===
{input_str}
=== FIN INPUT ===

=== OUTPUT del agente (caja.sections.{station_id}) ===
{section_str}
=== FIN OUTPUT ===

CRITERIO DE AUDITORÍA:
1. La sección debe existir y NO estar vacía.
2. Debe alinear con la spec del PDF (no off-topic).
3. Debe respetar las restricciones del PDF (REGLAs duras, formato esperado, campos obligatorios).
4. Debe ser coherente con el input_start.
5. NO debe haber violado REGLAs activas del stack.

VEREDICTO — devuelve EXACTAMENTE una de estas dos formas:

APPROVE
[razón breve 1 línea]

O

BREAK_<CODIGO_CORTO>
[razón concreta · qué corregir · 1-2 líneas]

NO suavices. NO permitas pasar lo dudoso. Si dudas → BREAK."""
