"""
Preprocesamiento reproducible y metadata de unidades:

- normaliza columnas
- detecta columna fecha robustamente
- convierte fechas
- detecta posibles porcentajes (0..1 vs 0..100)
- permite filtrar por mes (YYYY-MM)
- deja listo el DataFrame para modelado (pero NO elimina columnas no numéricas)

Funciones clave:
- normalize_columns
- detect_date_column
- coerce_date
- scale_possible_percentages
- select_numeric
- filter_month
- preprocess_dataframe  (punto de entrada público)
"""

import pandas as pd
import numpy as np
from typing import Optional, Tuple

POSSIBLE_DATE_COLS = [
    "fecha_completa",
    "fecha",
    "fecha_gestion",
    "fecha_gest",
    "dia",
    "día",
    "day",
]


def normalize_columns(df: pd.DataFrame) -> pd.DataFrame:
    """Normaliza nombres de columnas: minúsculas, sin espacios, sin guiones."""
    df = df.copy()
    df.columns = (
        df.columns.astype(str)
        .str.strip()
        .str.lower()
        .str.replace(" ", "_")
        .str.replace("-", "_")
    )
    return df


def detect_date_column(df: pd.DataFrame) -> Optional[str]:
    """
    Intenta detectar una columna de fecha:
    1) nombres típicos
    2) dtype datetime
    3) columnas que contengan 'fecha' o 'date'
    """
    cols = list(df.columns)

    # 1) nombres exactos conocidos
    for c in cols:
        lc = c.lower()
        if lc in POSSIBLE_DATE_COLS:
            return c

    # 2) tipo datetime
    for c in cols:
        if pd.api.types.is_datetime64_any_dtype(df[c]):
            return c

    # 3) nombre que contenga 'fecha' o 'date'
    for c in cols:
        if "fecha" in c.lower() or "date" in c.lower():
            return c

    return None


def coerce_date(df: pd.DataFrame, date_col: str, new_col: str = "fecha_completa") -> pd.DataFrame:
    """Crea una columna de fecha normalizada (datetime) a partir de date_col."""
    df = df.copy()
    df[new_col] = pd.to_datetime(df[date_col], dayfirst=True, errors="coerce")
    return df


def _detect_unit_series(series: pd.Series) -> str:
    """
    Detecta unidad típica de una serie numérica que aparenta ser porcentaje:
    - 'proportion' -> valores típicamente en 0..1
    - 'percent'    -> valores típicamente en 0..100
    - 'other'      -> no parece porcentaje
    """
    s = pd.to_numeric(series.dropna(), errors="coerce")
    if s.empty:
        return "other"

    p95 = np.nanpercentile(s, 95)
    mx = s.max()

    if p95 <= 1.1:
        return "proportion"
    if mx > 1.1:
        return "percent"
    return "other"


def scale_possible_percentages(df: pd.DataFrame) -> Tuple[pd.DataFrame, dict]:
    """
    Detecta columnas que parecen porcentajes y devuelve metadata con unidad.

    No transforma los valores (todavía) para no sorprender al usuario.
    Solo marca la unidad esperada:
        metadata[col] = 'proportion' | 'percent' | 'other'
    """
    df = df.copy()
    metadata = {}

    for c in df.columns:
        lc = c.lower()
        # Heurística de nombre de columna "tipo porcentaje"
        if any(k in lc for k in ["%", "porcentaje", "tasa", "nivel", "%_", "pct", "rate"]):
            try:
                unit = _detect_unit_series(df[c])
                metadata[c] = unit
            except Exception:
                metadata[c] = "other"
        else:
            metadata[c] = "other"

    return df, metadata


def select_numeric(df: pd.DataFrame) -> pd.DataFrame:
    """Devuelve solo columnas numéricas."""
    return df.select_dtypes(include=[np.number]).copy()


def filter_month(df: pd.DataFrame, month_str: str, date_col: str = "fecha_completa") -> pd.DataFrame:
    """
    Filtra por mes (YYYY-MM) usando la columna de fecha indicada.

    Si el formato no es válido o la columna no existe, devuelve el DF sin cambios.
    """
    m = pd.to_datetime(month_str + "-01", errors="coerce")
    if pd.isna(m):
        return df
    if date_col not in df.columns:
        return df

    return df[df[date_col].dt.to_period("M") == m.to_period("M")]


def preprocess_dataframe(df: pd.DataFrame, month: Optional[str] = None) -> pd.DataFrame:
    """
    Punto de entrada público de preprocesamiento.

    - Normaliza nombres de columnas.
    - Intenta detectar columna de fecha y la convierte a datetime (fecha_completa).
    - Si se pasa `month='YYYY-MM'`, filtra por ese mes.
    - Detecta posibles columnas porcentaje y guarda metadata (por ahora no se retorna, pero se puede exponer en futuras versiones).
    - Devuelve un DataFrame listo para usar (incluye numéricas y no numéricas).

    IMPORTANTE:
    - No elimina columnas no numéricas (para no romper flujos existentes).
    - Tu CLI y Notebooks siguen funcionando tal cual, porque luego hacen select_dtypes.
    """
    if not isinstance(df, pd.DataFrame):
        raise TypeError("preprocess_dataframe espera un pandas.DataFrame como entrada.")

    # 1) Normalizar nombres
    df_proc = normalize_columns(df)

    # 2) Detectar y convertir fecha
    date_col = detect_date_column(df_proc)
    if date_col is not None:
        df_proc = coerce_date(df_proc, date_col, new_col="fecha_completa")

        # 3) Filtro por mes (opcional)
        if month is not None:
            df_proc = filter_month(df_proc, month, date_col="fecha_completa")

    # 4) Detectar posibles porcentajes (metadata interna, por ahora no la devolvemos)
    df_proc, _units = scale_possible_percentages(df_proc)

    # 5) Devolvemos DF preprocesado
    return df_proc
