"""skills/analizador_combo.py — ANALIZAR (estación 2)

Cache lookup + faster-whisper transcripción + frame extraction + LLM extracción combo.
Skill canónica: analizador-combo-completo.

Backend: Python puro (faster-whisper) + claude CLI subprocess (combo extraction LLM).

Inputs:
  caja.descargar.video_path (output de la estación previa)
  caja.input_start.{product, country, saturated}

Output:
  caja.sections.analizar = {
    transcription, word_count, duration_seconds, language_detected,
    combo_completo: {awareness, sofisticacion, jergas, deseo_principal, avatar, ...},
    cache_hit: bool
  }
"""
from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
from skills._lib import SkillContext, SkillResult


def run(inputs: dict, context: SkillContext) -> SkillResult:
    caja = context.read_caja()
    desc = caja.get('sections', {}).get('descargar') or {}
    video_path = desc.get('video_path')
    inp = caja.get('input_start', {})
    product = inp.get('product', 'unknown')
    country = (inp.get('country') or 'es').lower()[:2]

    if not video_path or not Path(video_path).exists():
        return SkillResult(ok=False, errors=['video_path no existe — DESCARGAR debió correr antes'])

    # PASO 1: cache lookup
    cache_paths = [
        Path(rf'C:/Users/ferna/proyecto cero/_productos/{product}/_analysis.json'),
        Path(rf'C:/Users/ferna/proyecto cero/_productos/{product.lower()}/_analysis.json'),
    ]
    for cp in cache_paths:
        if cp.exists():
            try:
                cached = json.loads(cp.read_text(encoding='utf-8'))
                ts = cached.get('timestamp', '2000-01-01T00:00').replace('Z', '+00:00')
                age_days = (datetime.now() - datetime.fromisoformat(ts.replace('+00:00', ''))).days
                if age_days < 30:
                    context.write_section('analizar', {**cached, 'cache_hit': True, 'cache_path': str(cp), 'age_days': age_days}, mode='replace', actor='analizador-combo-completo')
                    return SkillResult(ok=True, output={'cache_hit': True, 'cache_path': str(cp)})
            except Exception:
                pass

    # PASO 2: faster-whisper transcripción
    try:
        from faster_whisper import WhisperModel
    except ImportError:
        return SkillResult(ok=False, errors=['faster_whisper no instalado · pip install faster-whisper'])

    try:
        model = WhisperModel('medium', device='cpu', compute_type='int8')
        segments, info = model.transcribe(video_path, language=country, beam_size=5, vad_filter=True)
        full_text = ' '.join(s.text.strip() for s in segments)
        word_count = len(full_text.split())
        if word_count < 20:
            return SkillResult(ok=False, errors=[f'Transcripción <20 palabras ({word_count}) — audio corrupto?'])
    except Exception as e:
        return SkillResult(ok=False, errors=[f'Whisper falló: {e}'])

    # PASO 3: combo extraction vía claude CLI
    saturated = inp.get('saturated', False)
    prompt = build_combo_prompt(full_text, product, country, saturated)
    res = context.claude_cli(prompt, model='claude-opus-4-7', timeout_s=180)
    if not res.get('ok'):
        return SkillResult(ok=False, errors=[f'claude CLI falló combo extraction: {res.get("stderr") or res.get("error")}'])

    # Parsear combo del output
    combo = extract_json_from_claude_output(res.get('stdout', ''))

    analysis = {
        'timestamp': datetime.now().isoformat(),
        'product': product,
        'video_path': video_path,
        'country': inp.get('country'),
        'duration_seconds': info.duration if hasattr(info, 'duration') else None,
        'transcription': full_text,
        'word_count': word_count,
        'language_detected': getattr(info, 'language', None),
        'combo_completo': combo,
        'cache_hit': False,
    }

    # Guardar para futuros lookups
    out_dir = Path(rf'C:/Users/ferna/proyecto cero/_productos/{product}')
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / '_analysis.json').write_text(json.dumps(analysis, ensure_ascii=False, indent=2), encoding='utf-8')

    context.write_section('analizar', analysis, mode='replace', actor='analizador-combo-completo')
    return SkillResult(ok=True, output={'word_count': word_count, 'combo_completo': combo})


def build_combo_prompt(transcription: str, product: str, country: str, saturated: bool) -> str:
    sat_note = 'Mercado SATURADO (sof nivel 4) — necesitamos formato más currado.' if saturated else 'Mercado normal.'
    return f"""Analiza este video ganador para el producto "{product}" en mercado {country.upper()}. {sat_note}

Extrae el COMBO COMPLETO en JSON estricto. Estructura obligatoria:
{{
  "awareness": "<Unaware|Problem Aware|Solution Aware|Product Aware|Most Aware> (Schwartz)",
  "sofisticacion": "<1-5>",
  "jergas": ["<jerga1>", "<jerga2>", "..."],
  "deseo_principal": "<frase corta>",
  "avatar": {{"edad": "<rango>", "genero": "<m/f/ambos>", "contexto": "<vida>", "profesion": "<x>"}},
  "temporalidad": "<urgencia + estacionalidad + etapa de vida>",
  "fuerza_cambio": "<qué empuja a actuar AHORA>",
  "antagonistas_nombrados": ["<x>", "..."],
  "formato_angulo": "<descripción>",
  "mecanismo_revelado": "<si aplica>",
  "estructura_narrativa": "<actos>",
  "duracion_clase": "<corto|medio|largo>"
}}

TRANSCRIPCIÓN DEL VIDEO:
\"\"\"
{transcription[:8000]}
\"\"\"

Responde SOLO con el JSON, sin texto antes ni después."""


def extract_json_from_claude_output(stdout: str) -> dict:
    """Intenta parsear JSON del output de claude. Si claude añade prosa, busca el bloque JSON."""
    s = stdout.strip()
    # Quitar markdown fences si los hay
    if s.startswith('```'):
        s = s.split('```', 2)
        if len(s) >= 2:
            s = s[1]
            if s.startswith('json'):
                s = s[4:].strip()
    # Buscar { ... } externo
    try:
        return json.loads(s)
    except Exception:
        pass
    # Fallback: buscar primer '{' y último '}'
    try:
        first = s.index('{')
        last = s.rindex('}')
        return json.loads(s[first:last+1])
    except Exception:
        return {'_raw_unparseable': s[:500]}
