"""
Validadores reutilizables para import CSV de tipos de cambio.
"""
from datetime import datetime, time
from decimal import Decimal
from typing import Dict, Any, Optional, List

DATE_FORMATS: List[str] = ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d-%m-%Y", "%Y/%m/%d"]

# Valores que envía el frontend (globalCsvParams dateFormat) -> formato strptime
# Cuando el usuario elige un formato en "Parámetros globales", solo se aceptan fechas en ese formato.
DATE_FORMAT_PREFERENCE_MAP: Dict[str, str] = {
    "dd/mm/yyyy": "%d/%m/%Y",
    "mm/dd/yyyy": "%m/%d/%Y",
    "yyyy-mm-dd": "%Y-%m-%d",
}

CURRENCY_MAX = 7


def parse_date(val: Optional[str], date_format_preference: Optional[str] = None) -> Optional[datetime]:
    """
    Parsea fecha. Si date_format_preference está definido (formato elegido en el frontend),
    solo se acepta ese formato; si la cadena no coincide, se rechaza.
    Si no hay preferencia, se intentan todos los formatos (retrocompatibilidad).
    """
    if not val or not str(val).strip():
        return None
    raw = str(val).strip()
    if date_format_preference and date_format_preference in DATE_FORMAT_PREFERENCE_MAP:
        fmt = DATE_FORMAT_PREFERENCE_MAP[date_format_preference]
        try:
            parsed = datetime.strptime(raw, fmt)
            return datetime.combine(parsed.date(), time.min)
        except ValueError:
            return None
    for fmt in DATE_FORMATS:
        try:
            parsed = datetime.strptime(raw, fmt)
            return datetime.combine(parsed.date(), time.min)
        except ValueError:
            continue
    return None


def parse_decimal_positive(val: Optional[str]) -> Optional[Decimal]:
    if not val or not str(val).strip():
        return None
    try:
        v = float(str(val).strip().replace(",", "."))
        if v <= 0:
            return None
        return Decimal(str(round(v, 6)))
    except (ValueError, TypeError):
        return None


def check_required_date(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    fecha_raw = (row.get("FECHA") or "").strip()
    if not fecha_raw:
        return {"line": line_num, "col": "FECHA", "msg": "Requerido"}
    if parse_date(fecha_raw) is None:
        return {"line": line_num, "col": "FECHA", "msg": "Formato de fecha inválido (use YYYY-MM-DD o DD/MM/YYYY)"}
    return None


def check_required_value_positive(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    valor_raw = (row.get("VALOR") or "").strip()
    if not valor_raw:
        return {"line": line_num, "col": "VALOR", "msg": "Requerido"}
    try:
        v = float(valor_raw.replace(",", "."))
        if v <= 0:
            return {"line": line_num, "col": "VALOR", "msg": "Debe ser mayor que cero"}
    except ValueError:
        return {"line": line_num, "col": "VALOR", "msg": "Debe ser un número"}
    return None


def check_optional_max_length(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]:
    val = (row.get(col) or "").strip()
    if not val:
        return None
    if len(val) > max_len:
        return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"}
    return None


FECHA_MAX_LEN = 10
MSG_FECHA_LONGITUD = "Error: (Col. A) La Fecha: {fecha} supera la longitud de caracteres."
MSG_FECHA_LONGITUD_SOLUCION = "Capturar en la columna A el campo Fecha con este formato ##/##/####."
MSG_FECHA_DIA_INVALIDO = "Error: (Col. A) El día {dia} de la Fecha: {fecha} no es válido para el mes."
MSG_FECHA_DIA_SOLUCION = "Capturar correctamente en la columna A el día del campo Fecha, con este formato ##/##/####. (Día/Mes/Año)"
MSG_FECHA_MES_INVALIDO = "Error: (Col. A) El mes {mes} de la Fecha: {fecha} es mayor a 12 esto no es valido."
MSG_FECHA_MES_SOLUCION = "Capturar correctamente en la columna A el mes del campo Fecha, con este formato ##/##/####. (Día/Mes/Año)"

# Etiquetas para mensaje cuando no coincide con el formato elegido
DATE_FORMAT_LABELS: Dict[str, str] = {
    "dd/mm/yyyy": "DD/MM/YYYY (Día/Mes/Año)",
    "mm/dd/yyyy": "MM/DD/YYYY (Mes/Día/Año)",
    "yyyy-mm-dd": "YYYY-MM-DD (Año-Mes-Día)",
}


def validate_fecha_clarion(
    raw_fecha: str, line_num: int, date_format_preference: Optional[str] = None
) -> Optional[Dict[str, Any]]:
    """
    Valida la fecha según reglas Clarion: longitud ≤ 10, día válido para el mes, mes ≤ 12.
    Sin límite de año. Si date_format_preference está definido (ej. dd/mm/yyyy), se prioriza ese formato.
    """
    if not raw_fecha or not str(raw_fecha).strip():
        return None
    raw = str(raw_fecha).strip()
    if len(raw) > FECHA_MAX_LEN:
        return {
            "line": line_num,
            "col": "FECHA",
            "msg": f"{MSG_FECHA_LONGITUD.format(fecha=raw)} {MSG_FECHA_LONGITUD_SOLUCION}",
        }
    parsed = parse_date(raw, date_format_preference)
    if parsed is None:
        format_label = (
            DATE_FORMAT_LABELS.get(date_format_preference, "##/##/####")
            if date_format_preference
            else "##/##/####"
        )
        return {
            "line": line_num,
            "col": "FECHA",
            "msg": f"Error: (Col. A) La Fecha: {raw} no coincide con el formato elegido ({format_label}). {MSG_FECHA_LONGITUD_SOLUCION}",
        }
    d = parsed.date()
    if d.month > 12:
        return {
            "line": line_num,
            "col": "FECHA",
            "msg": f"{MSG_FECHA_MES_INVALIDO.format(mes=d.month, fecha=raw)} {MSG_FECHA_MES_SOLUCION}",
        }
    return None
