"""
Narrator de Leonex — narrativa en lenguaje natural de cada decision del Executor.

Para cada accion del Executor (executed, vetoed, skipped, rejected, planned)
genera un parrafo legible que explica:
    - Que se hizo (o no se hizo)
    - Por que: factores cuantitativos (Meta, Kelly, Vol, HRP) y cualitativos
      (Judge: racha, correlacion, drift, risk)
    - Datos numericos concretos para auditoria

Backend:
    1) Si Ollama esta corriendo en http://localhost:11434 con un modelo
       pequeno tipo llama3.2:3b → usa el LLM para fluidez.
    2) Si no, plantilla Python con f-strings. Igual de informativa, menos
       fluida.

API:
    narrate_action(action, plan_entry, judge_verdict, ollama_model=None) -> str
"""

from __future__ import annotations

import json
import logging
import os
from pathlib import Path
from typing import Optional

import urllib.request
import urllib.error


OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
DEFAULT_OLLAMA_MODEL = "llama3.2:3b"
OLLAMA_TIMEOUT_S = 8


# ─────────────────────────────────────────────────────────────────────────────
# Ollama HTTP client (minimalist)
# ─────────────────────────────────────────────────────────────────────────────

def _ollama_available(model: str = DEFAULT_OLLAMA_MODEL, timeout: int = 3) -> bool:
    """Comprueba si Ollama corre y el modelo esta cargado."""
    try:
        req = urllib.request.Request(f"{OLLAMA_URL}/api/tags")
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        names = [m.get("name", "") for m in data.get("models", [])]
        return any(model in n for n in names)
    except Exception:
        return False


def _ollama_generate(prompt: str, model: str = DEFAULT_OLLAMA_MODEL,
                     timeout: int = OLLAMA_TIMEOUT_S) -> Optional[str]:
    """Llama a /api/generate de Ollama y devuelve el texto, o None si falla."""
    try:
        payload = json.dumps({
            "model": model, "prompt": prompt, "stream": False,
            "options": {"temperature": 0.3, "num_predict": 220},
        }).encode("utf-8")
        req = urllib.request.Request(
            f"{OLLAMA_URL}/api/generate",
            data=payload, method="POST",
            headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        text = (data.get("response") or "").strip()
        return text or None
    except Exception:
        return None


# ─────────────────────────────────────────────────────────────────────────────
# Plantilla Python (fallback)
# ─────────────────────────────────────────────────────────────────────────────

def _template_narrative(action, plan_entry=None, judge_verdict=None) -> str:
    """Genera narrativa por plantilla cuando Ollama no esta disponible."""
    ticker = getattr(action, "ticker", "?")
    status = (getattr(action, "status", "") or "unknown").lower()
    qty = float(getattr(action, "qty", 0) or 0)
    price = float(getattr(action, "requested_price", 0) or 0)
    prob = float(getattr(action, "prob_win", 0) or 0)
    notes = getattr(action, "notes", "") or ""
    side = (getattr(action, "side", "") or "").upper()

    prob_str = f"prob_win={prob:.3f}" if prob > 0 else ""
    pros = ""
    cons = ""
    judge_verdict_str = ""
    if judge_verdict is not None:
        judge_verdict_str = f"Judge: {judge_verdict.verdict}. "
        if getattr(judge_verdict, "reasons_pro", None):
            pros = " · ".join(judge_verdict.reasons_pro[:3])
        if getattr(judge_verdict, "reasons_against", None):
            cons = " · ".join(judge_verdict.reasons_against[:3])

    notional = qty * price

    if status == "submitted" or status == "filled":
        body = (f"{ticker} EJECUTADO ({side}). Compradas {qty:.6f} unidades "
                f"a ${price:.2f} (notional ${notional:,.2f}). {prob_str}. "
                f"{judge_verdict_str}")
        if pros:
            body += f"A favor: {pros}. "
        if cons:
            body += f"Reservas registradas: {cons}. "
        body += f"Detalles: {notes[:120]}"
    elif status == "vetoed":
        body = (f"{ticker} VETADO por el Judge. Aunque el Meta dio {prob_str}, "
                f"el contexto cualitativo no era favorable. Razones: {cons or notes[:120]}. "
                f"No se envio orden.")
    elif status == "planned":
        body = (f"{ticker} PLANEADO ({side}) pero no enviado (modo dry-run o "
                f"approve manual pendiente). {prob_str}. {judge_verdict_str} "
                f"Sizing: {notes[:120]}")
    elif status == "skipped":
        body = (f"{ticker} SALTADO. Motivo: {notes[:160]}. No se considero "
                f"para el envio.")
    elif status == "rejected":
        body = (f"{ticker} RECHAZADO por Alpaca al enviar. Posibles motivos: "
                f"buying_power insuficiente, ticker no operable, o "
                f"restriccion del broker. Detalle: {notes[:160]}")
    else:
        body = f"{ticker} estado={status}. {notes[:160]}"

    return body.strip()


# ─────────────────────────────────────────────────────────────────────────────
# Ollama prompt builder
# ─────────────────────────────────────────────────────────────────────────────

def _build_ollama_prompt(action, plan_entry=None, judge_verdict=None) -> str:
    ticker = getattr(action, "ticker", "?")
    status = getattr(action, "status", "unknown")
    qty = float(getattr(action, "qty", 0) or 0)
    price = float(getattr(action, "requested_price", 0) or 0)
    prob = float(getattr(action, "prob_win", 0) or 0)
    notes = getattr(action, "notes", "") or ""
    side = getattr(action, "side", "") or ""

    regime = getattr(plan_entry, "regime", "?") if plan_entry else "?"
    rsi = getattr(plan_entry, "rsi_14", 0) if plan_entry else 0
    mom = getattr(plan_entry, "momentum_20", 0) if plan_entry else 0

    pros = []
    cons = []
    verdict = "?"
    if judge_verdict is not None:
        verdict = getattr(judge_verdict, "verdict", "?")
        pros = getattr(judge_verdict, "reasons_pro", []) or []
        cons = getattr(judge_verdict, "reasons_against", []) or []

    pros_str = "\n".join(f"  - {p}" for p in pros[:5]) or "  (ninguno explicitado)"
    cons_str = "\n".join(f"  - {c}" for c in cons[:5]) or "  (ninguno explicitado)"

    return (
        "Eres un analista cuantitativo. Escribe en espanol un parrafo breve "
        "(3-4 frases) que explique al usuario en lenguaje natural por que "
        "se tomo esta decision de trading. Se preciso, evita marketing.\n\n"
        f"Activo: {ticker}\n"
        f"Estado de la decision: {status}\n"
        f"Lado: {side}\n"
        f"Cantidad: {qty}\n"
        f"Precio: ${price:.2f}\n"
        f"Probabilidad de ganar (Meta): {prob:.3f}\n"
        f"Regimen del activo: {regime}\n"
        f"RSI 14: {rsi}\n"
        f"Momentum 20d: {mom*100:.2f}%\n"
        f"Veredicto del Judge cualitativo: {verdict}\n"
        f"Factores a favor:\n{pros_str}\n"
        f"Factores en contra / reservas:\n{cons_str}\n"
        f"Detalles internos: {notes}\n\n"
        "Parrafo:"
    )


# ─────────────────────────────────────────────────────────────────────────────
# API publica
# ─────────────────────────────────────────────────────────────────────────────

_OLLAMA_AVAILABLE: Optional[bool] = None


def narrate_action(action, plan_entry=None, judge_verdict=None,
                   ollama_model: Optional[str] = None,
                   force_template: bool = False,
                   logger: Optional[logging.Logger] = None) -> str:
    """Devuelve narrativa en lenguaje natural de la accion.

    Args:
        action: ExecutorAction
        plan_entry: PlanEntry (opcional, para contexto)
        judge_verdict: JudgeVerdict (opcional, para reasons pro/con)
        ollama_model: si None usa default. Si False/"" fuerza fallback.
        force_template: True para forzar fallback (testing).
    """
    global _OLLAMA_AVAILABLE
    if logger is None:
        logger = logging.getLogger("narrator")

    if force_template or ollama_model in (False, ""):
        return _template_narrative(action, plan_entry, judge_verdict)

    model = ollama_model or DEFAULT_OLLAMA_MODEL
    if _OLLAMA_AVAILABLE is None:
        _OLLAMA_AVAILABLE = _ollama_available(model)
        if _OLLAMA_AVAILABLE:
            logger.info("Narrator: usando Ollama (%s)", model)
        else:
            logger.info("Narrator: Ollama no disponible, plantilla Python como fallback")
    if not _OLLAMA_AVAILABLE:
        return _template_narrative(action, plan_entry, judge_verdict)

    prompt = _build_ollama_prompt(action, plan_entry, judge_verdict)
    text = _ollama_generate(prompt, model)
    if not text:
        # Fallo puntual del modelo: caemos a plantilla
        return _template_narrative(action, plan_entry, judge_verdict)
    return text
