"""
Validadores reutilizables para import CSV de vehículos (longitudes, decimal, fecha aseguradora).
Paridad Clarion: VALIDACIONES_TRANSPORTE, código entidad C/I/A/B, catálogos tipo/país/estado, desfase.
"""
import re
from decimal import Decimal
from typing import Dict, Any, Optional, Set, Tuple

# Max lengths from Vehicle model (a76.vehicle). Clarion Col A máx 15; modelo actual 14.
MAX_LEN = {
    "vehicle_key": 14,
    "ace_vehicle_key": 10,
    "transporter_key": 23,
    "transport_identifier": 30,
    "transport_type": 2,
    "entity_code": 1,
    "transponder_number": 16,
    "dot_number": 8,
    "plate_number": 17,
    "city": 30,
    "state": 30,
    "country": 3,
    "seal": 49,
    "insurance_company_name": 30,
    "insurance_number": 20,
    "series": 30,
}


def check_required(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 {"line": line_num, "col": col, "msg": "Requerido"}
    if len(val) > max_len:
        return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"}
    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"Máximo {max_len} caracteres"}
    return None


def parse_decimal(val: Any) -> Optional[Decimal]:
    if val is None or (isinstance(val, str) and not val.strip()):
        return None
    try:
        s = str(val).strip().replace(",", "")
        return Decimal(s)
    except Exception:
        return None


def parse_insurance_date(val: Any) -> Optional[int]:
    """Parse FECHA DE ASEGURADORA to integer yyyymmdd. Tolerates DD/MM/YYYY, YYYY-MM-DD, or YYYYMMDD."""
    if val is None or (isinstance(val, str) and not val.strip()):
        return None
    s = str(val).strip()
    if not s:
        return None
    if re.match(r"^\d{8}$", s):
        try:
            return int(s)
        except ValueError:
            pass
    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):
                    pass
            break
    return None


def check_optional_decimal(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]:
    val = row.get(col)
    if val is None or (isinstance(val, str) and not val.strip()):
        return None
    if parse_decimal(val) is None:
        return {"line": line_num, "col": col, "msg": "Debe ser numérico"}
    return None


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


# --- Clarion VALIDACIONES_TRANSPORTE: dominio y catálogos ---

CODIGO_ENTIDAD_VALIDOS = {"C", "I", "A", "B"}


def check_codigo_entidad_valores(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col F: Si CODIGO DE ENTIDAD no vacío, debe ser C, I, A o B (Clarion)."""
    val = (row.get("CODIGO DE ENTIDAD") or "").strip().upper()
    if not val:
        return None
    if val in CODIGO_ENTIDAD_VALIDOS:
        return None
    return {
        "line": line_num,
        "col": "CODIGO DE ENTIDAD",
        "msg": f"Error: (Col. F) El Codigo de Entidad: {val} es incorrecto.",
        "solution": "Capturar en columna F el Codigo de Entidad correcto, C, I, A ó B.",
    }


def check_codigo_entidad_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """VALIDA_TODA: Col F (Codigo de Entidad) obligatorio cuando registro es nuevo."""
    val = (row.get("CODIGO DE ENTIDAD") or "").strip()
    if val:
        return None
    return {
        "line": line_num,
        "col": "CODIGO DE ENTIDAD",
        "msg": "Existen campos vacios que son obligatorios, es la (Col.F) Codigo de Entidad.",
        "solution": "Revisar la línea del archivo y capturar los campos con la información correcta.",
    }


def check_transport_type_catalog(
    row: Dict[str, Any],
    line_num: int,
    valid_transport_codes: Optional[Set[str]] = None,
) -> Optional[Dict[str, Any]]:
    """Col E: Si TIPO TRANSPORTE no vacío, debe existir en catálogo (GTipoTransportes)."""
    val = (row.get("TIPO TRANSPORTE") or "").strip()
    if not val or valid_transport_codes is None:
        return None
    if val.upper() in valid_transport_codes:
        return None
    return {
        "line": line_num,
        "col": "TIPO TRANSPORTE",
        "msg": f"Error: (Col. E) El Tipo de Transporte: {val} es incorrecto.",
        "solution": "Capturar en columna E algun Tipo de Transporte correcto.",
    }


def check_pais_catalog_vehicles(
    row: Dict[str, Any],
    line_num: int,
    valid_country_ame: Optional[Set[str]] = None,
) -> Optional[Dict[str, Any]]:
    """Col L: Si PAIS no vacío, debe ser clave americana (2 chars) y existir en catálogo (GPaises.Pais_Ame)."""
    val = (row.get("PAIS") or "").strip()
    if not val or valid_country_ame is None:
        return None
    val_upper = val.upper()
    if len(val) > 2:
        return {
            "line": line_num,
            "col": "PAIS",
            "msg": f"Error: (Celda L{line_num}) El Pais: {val} Es Incorrecto",
            "solution": "Capturar en columna L un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).",
        }
    if val_upper in valid_country_ame:
        return None
    return {
        "line": line_num,
        "col": "PAIS",
        "msg": f"Error: (Col. L) El Pais: {val} es incorrecto.",
        "solution": "Capturar en columna L el Pais del Transporte en Clave Americana.",
    }


def check_estado_catalog(
    row: Dict[str, Any],
    line_num: int,
    state_descriptions_upper: Optional[Set[str]] = None,
) -> Optional[Dict[str, Any]]:
    """Col K: Si ESTADO no vacío, debe existir en catálogo (GEstados). Si hay estado, PAIS no puede estar vacío."""
    val = (row.get("ESTADO") or "").strip()
    if not val or state_descriptions_upper is None:
        return None
    val_upper = val.upper()
    if val_upper not in state_descriptions_upper:
        return {
            "line": line_num,
            "col": "ESTADO",
            "msg": f"Error: (Celda K{line_num}) El Estado: {val} es incorrecto.",
            "solution": "Capturar en columna K el Estado del Transporte en Clave Americana o Nombre Completo.",
        }
    # Si existe estado, PAIS no puede estar vacío (Clarion)
    pais = (row.get("PAIS") or "").strip()
    if not pais:
        return {
            "line": line_num,
            "col": "PAIS",
            "msg": f"Error: (Celda K{line_num}) El Estado: {val} No esta ligado a ningun Pais.",
            "solution": "Capturar en columna L un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).",
        }
    return None


def check_estado_pais_consistency(
    row: Dict[str, Any],
    line_num: int,
    state_country_set: Optional[Set[Tuple[str, str]]] = None,
) -> Optional[Dict[str, Any]]:
    """Si hay ESTADO y PAIS, validar que el estado pertenezca al país (Clarion GEstados-GPaises)."""
    estado = (row.get("ESTADO") or "").strip()
    pais = (row.get("PAIS") or "").strip().upper()
    if not estado or not pais or state_country_set is None:
        return None
    key = (pais, estado.upper())
    if key in state_country_set:
        return None
    return {
        "line": line_num,
        "col": "ESTADO",
        "msg": f"Error: (Col. K) EL Estado: {estado} no pertenece al Pais: {pais}.",
        "solution": "Capturar en columna K un Estado que pertenesca al Pais de la columna L.",
    }


def check_desfase_vehicles(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Si COL_EXTRA (columna 18 / Col R) tiene valor → advertencia de desfase (Clarion, no bloqueante por defecto)."""
    val = (row.get("COL_EXTRA") or "").strip()
    if not val:
        return None
    return {
        "line": line_num,
        "col": "COL_EXTRA",
        "msg": "Advertencia: Podría existir un desfase en esta línea.",
        "solution": "Revisar esta línea del archivo CSV y verificar cada campo este en la posicion correcta.",
        "warning": True,
    }
