"""Convierte una serie de niveles (precios) a retornos (grupo eda).

Funcion pura y determinista que transforma una serie de niveles en una serie de
retornos, simples o logaritmicos. Motivada por Lopez de Prado ("Advances in
Financial ML") y Hamilton ("Time Series Analysis"): las series de precios son no
estacionarias (raiz unitaria), de modo que correlacionarlas o modelarlas sobre
sus niveles produce regresion espuria (Granger-Newbold). Los retornos son
(aproximadamente) estacionarios y son la unidad correcta para correlacionar,
medir volatilidad o ajustar modelos.
"""

from __future__ import annotations

import math


def _clean(values: list) -> list[float]:
    """Conserva solo valores numericos finitos, descartando None/NaN/no-numericos.

    A diferencia de otras funciones del grupo, aqui el ORDEN importa (es una
    serie temporal), pero un hueco intermedio rompe el calculo de retorno
    consecutivo; por eso se descartan los no-validos y el retorno se calcula
    sobre los puntos validos restantes en su orden original.
    """
    out: list[float] = []
    for v in values:
        if v is None or isinstance(v, bool):
            continue
        if not isinstance(v, (int, float)):
            continue
        x = float(v)
        if math.isnan(x) or math.isinf(x):
            continue
        out.append(x)
    return out


def to_returns(values: list, method: str = "log") -> dict:
    """Convierte una serie de niveles (precios) a retornos.

    Calcula el retorno entre observaciones consecutivas de la serie limpia:

    - ``method="log"``:  ``r_t = ln(p_t / p_{t-1})``  (retorno logaritmico).
      Aditivo en el tiempo y simetrico; es el preferido para modelar. Requiere
      precios estrictamente positivos: si aparece un valor <= 0 ese paso se
      marca como invalido (``None`` en la serie) y se cuenta en ``n_skipped``.
    - ``method="simple"``:  ``r_t = p_t / p_{t-1} - 1``  (retorno aritmetico).
      Admite valores negativos; solo se invalida el paso si ``p_{t-1} == 0``
      (division por cero).

    Funcion pura y determinista: no hace I/O, no muta los inputs.

    Args:
        values: serie de niveles (precios) en orden cronologico. None/NaN/
            infinitos/no-numericos se descartan antes de calcular.
        method: ``"log"`` (default) para retornos logaritmicos o ``"simple"``
            para retornos aritmeticos.

    Returns:
        Con menos de 2 puntos validos (no hay ningun par consecutivo) devuelve
        ``{"n": n, "note": "datos insuficientes", "returns": []}``.

        Si ``method`` no es ``"log"`` ni ``"simple"`` devuelve
        ``{"note": "method debe ser 'log' o 'simple'", "returns": []}``.

        En otro caso un dict con::

            {
              "method": str,
              "n_levels": int,        # niveles validos de entrada
              "returns": [float|None],# un retorno por par consecutivo (None si invalido)
              "n_returns": int,       # retornos validos (no None)
              "n_skipped": int,       # pasos invalidados (log de no-positivo, div/0)
              "mean": float,          # media de los retornos validos
              "std": float,           # desviacion tipica (ddof=0) de los validos
              "min": float,
              "max": float,
            }

        Si todos los pasos resultan invalidos, ``mean/std/min/max`` son ``None``.
    """
    if method not in ("log", "simple"):
        return {"note": "method debe ser 'log' o 'simple'", "returns": []}

    clean = _clean(values)
    n = len(clean)

    if n < 2:
        return {"n": n, "note": "datos insuficientes", "returns": []}

    returns: list[float | None] = []
    n_skipped = 0
    for prev, cur in zip(clean[:-1], clean[1:]):
        if method == "log":
            if prev <= 0.0 or cur <= 0.0:
                returns.append(None)
                n_skipped += 1
                continue
            returns.append(math.log(cur / prev))
        else:  # simple
            if prev == 0.0:
                returns.append(None)
                n_skipped += 1
                continue
            returns.append(cur / prev - 1.0)

    valid = [r for r in returns if r is not None]
    if valid:
        mean = sum(valid) / len(valid)
        var = sum((r - mean) ** 2 for r in valid) / len(valid)
        std = math.sqrt(var)
        vmin = min(valid)
        vmax = max(valid)
    else:
        mean = std = vmin = vmax = None

    return {
        "method": method,
        "n_levels": n,
        "returns": returns,
        "n_returns": len(valid),
        "n_skipped": n_skipped,
        "mean": mean if mean is None else float(mean),
        "std": std if std is None else float(std),
        "min": vmin if vmin is None else float(vmin),
        "max": vmax if vmax is None else float(vmax),
    }
