"""
Helpers reutilizables para validación de filas CSV (conductores).
Paridad Clarion: VALIDACIONES_CONDUCTOR, obligatorios A/C, catálogos transportista/países, Sexo M/F, Si/No, tipo identificación, desfase.
"""
import re
from typing import Dict, Any, Optional, Set


MAX_LEN = {
    # Alineado a a76.transporter.transporter_key (23); CSV Clarion histórico usaba claves cortas
    "transporter_key": 23,
    "driver_name": 80,
    "license_number": 29,
    "express_line_id": 17,
    "ace_id": 20,
    "gender": 1,
    "birth_country": 3,
    "hazardous_material_auth": 2,
    "hazardous_material_state": 30,
    "first_name": 20,
    "last_name": 20,
    "id_key1": 40,
    "id_number1": 20,
    "id_state1": 30,
    "id_country1": 3,
    "id_key2": 40,
    "id_number2": 20,
    "id_state2": 30,
    "id_country2": 3,
}

# Clarion: Col H Sexo M o F
SEXO_VALIDOS = {"M", "F"}

# Clarion: Col J Si o No (comparar en mayúsculas)
MATERIAL_PELIGROSO_VALIDOS = {"SI", "NO"}

# Clarion: Col N y Col R tipo identificación
FORMA_IDENTIFICACION_CLAVES = frozenset(
    {"ACW", "ALR", "BCP", "BCN", "CDN", "CON", "OTD", "REP", "RTP", "5J", "5K", "30"}
)

# Mapeo clave CSV → valor guardado en BD (Clarion QueCSV:ColumnaN/R)
FORMA_IDENTIFICACION_MAP = {
    "ACW": "ACW-Pasaporte",
    "ALR": "ALR-Residencia",
    "BCP": "BCP-Permiso Cruce",
    "BCN": "BCN-Acta Nacimiento",
    "CDN": "CDN-Ciudadania",
    "CON": "CON-CertificadoNaturalizacion",
    "OTD": "OTD-Otra Identificación",
    "REP": "REP-Permiso Reentrada",
    "RTP": "RTP-Permiso de Viaje",
    "5J": "5J - Licencia",
    "5K": "5K -Licencia",
    "30": "30 -Visa de EU",
}


def check_required(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]:
    val = (row.get(col) or "").strip()
    if not val:
        return {"line": line_num, "col": col, "msg": "Requerido"}
    return None


def check_max_length(
    row: Dict[str, Any],
    col: str,
    max_len: int,
    line_num: int,
    required: bool = False,
) -> Optional[Dict[str, Any]]:
    val = (row.get(col) or "").strip()
    if not val:
        if required:
            return {"line": line_num, "col": col, "msg": "Requerido"}
        return None
    if len(val) > max_len:
        return {"line": line_num, "col": col, "msg": f"Maximo {max_len} caracteres"}
    return None


def parse_int(val: Any) -> Optional[int]:
    if val is None or (isinstance(val, str) and not val.strip()):
        return None
    s = str(val).strip()
    if re.match(r"^\d+$", s):
        return int(s)
    if re.match(r"^\d+\.0+$", s):
        try:
            return int(float(s))
        except ValueError:
            return None
    return None


def check_int_positive(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]:
    v = parse_int(row.get(col))
    if v is None:
        return {"line": line_num, "col": col, "msg": "Debe ser numerico"}
    if v <= 0:
        return {"line": line_num, "col": col, "msg": "Debe ser mayor a 0"}
    return None


def parse_birth_date(val: Any) -> Optional[int]:
    if val is None or (isinstance(val, str) and not val.strip()):
        return None
    s = str(val).strip()
    if re.match(r"^\d{8}$", s):
        try:
            return int(s)
        except ValueError:
            return None
    if re.match(r"^\d+\.0+$", s):
        try:
            return int(float(s))
        except ValueError:
            return None
    for sep in ["/", "-", "."]:
        if sep in s:
            parts = s.split(sep)
            if len(parts) == 3:
                try:
                    a, b, c = [p.strip() for p in parts]
                    if len(c) == 4 and len(a) <= 2 and len(b) <= 2:
                        return int(c) * 10000 + int(b) * 100 + int(a)
                    if len(a) == 4 and len(b) <= 2 and len(c) <= 2:
                        return int(a) * 10000 + int(b) * 100 + int(c)
                except (ValueError, TypeError):
                    return None
            break
    return None


def check_optional_birth_date(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]:
    val = row.get(col)
    if val is None or not str(val).strip():
        return None
    if parse_birth_date(val) is None:
        return {
            "line": line_num,
            "col": col,
            "msg": "Formato de fecha invalido (use YYYYMMDD o DD/MM/YYYY)",
        }
    return None


def check_transportista_catalog(
    row: Dict[str, Any],
    line_num: int,
    valid_transporter_keys: Optional[Set[str]] = None,
) -> Optional[Dict[str, Any]]:
    """Col A: Si TRANSPORTISTA no vacío, debe existir en catálogo (GTransportista)."""
    val = (row.get("TRANSPORTISTA") or "").strip()
    if not val or valid_transporter_keys is None:
        return None
    if val.upper() in valid_transporter_keys:
        return None
    return {
        "line": line_num,
        "col": "TRANSPORTISTA",
        "msg": f"Error: (Col. A) La Clave de Transportista: {val} es incorrecto.",
        "solution": "Capturar en columna A un Transportista existente en catalogo.",
    }


def check_sexo_m_f(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col H: Si SEXO no vacío, debe ser M o F (Clarion)."""
    val = (row.get("SEXO") or "").strip()
    if not val:
        return None
    if val.upper() in SEXO_VALIDOS:
        return None
    conductor = (row.get("CLAVE CONDUCTOR") or "").strip() or "(Conductor)"
    return {
        "line": line_num,
        "col": "SEXO",
        "msg": f"Error: (Col. H) El Sexo: {val} del Conductor: {conductor} es incorrecto.",
        "solution": "Capturar en columna H el sexo correcto (M o F).",
    }


def check_pais_catalog_drivers(
    row: Dict[str, Any],
    col: str,
    line_num: int,
    valid_country_ame: Optional[Set[str]] = None,
    col_letter: str = "",
) -> Optional[Dict[str, Any]]:
    """Si col (PAIS NACIMIENTO, PAIS, PAIS 2) no vacío, debe ser clave americana en catálogo (GPaises.Pais_Ame)."""
    val = (row.get(col) or "").strip()
    if not val or valid_country_ame is None:
        return None
    val_upper = val.upper()
    if len(val) > 3:
        return {
            "line": line_num,
            "col": col,
            "msg": f"Error: ({col_letter}) El Pais: {val} es incorrecto.",
            "solution": "Capturar un Pais en clave americana (US, MX, etc.).",
        }
    if val_upper in valid_country_ame:
        return None
    return {
        "line": line_num,
        "col": col,
        "msg": f"Error: ({col_letter}) El Pais: {val} es incorrecto.",
        "solution": "Capturar en columna un Pais en clave americana.",
    }


def check_material_peligroso_si_no(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col J: TRANSPORTA MAT. PELIGROSO? debe ser Si o No (Clarion; comparar en mayúsculas)."""
    val = (row.get("TRANSPORTA MAT. PELIGROSO?") or "").strip()
    if not val:
        return None
    if val.upper() in MATERIAL_PELIGROSO_VALIDOS:
        return None
    return {
        "line": line_num,
        "col": "TRANSPORTA MAT. PELIGROSO?",
        "msg": "Error: (Col. J) La Autorizacion para el Manejo de Material Peligroso es incorrecta.",
        "solution": "Capturar en columna J Si o No la autorizacion.",
    }


def check_tipo_identificacion(
    row: Dict[str, Any],
    col: str,
    line_num: int,
    col_letter: str = "",
    primera_o_segunda: str = "Primera",
) -> Optional[Dict[str, Any]]:
    """Col N o R: Si FORMA IDENTIFICACION no vacío, debe ser clave válida (ACW, ALR, ...)."""
    val = (row.get(col) or "").strip()
    if not val:
        return None
    val_upper = val.upper()
    if val_upper in FORMA_IDENTIFICACION_CLAVES:
        return None
    return {
        "line": line_num,
        "col": col,
        "msg": f"Error: ({col_letter}) El Tipo de Identificacion: {val} de la {primera_o_segunda} Identificacion es incorrecto.",
        "solution": f"Capturar en columna {col_letter} una clave de Identificacion valida (ACW, ALR, BCP, BCN, CDN, CON, OTD, REP, RTP, 5J, 5K, 30).",
    }


def check_desfase_drivers(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Si COL_EXTRA (Col V) tiene valor -> advertencia de desfase (Clarion, no bloqueante)."""
    val = (row.get("COL_EXTRA") or "").strip()
    if not val:
        return None
    return {
        "line": line_num,
        "col": "COL_EXTRA",
        "msg": "Advertencia: Podria existir un desfase en esta linea.",
        "solution": "Revisar esta linea del archivo CSV y verificar cada campo este en la posicion correcta.",
        "warning": True,
    }
