"""
Validaciones comunes de fila para import CSV de pedimentos.
Paridad Clarion: desfase (Col N), Cols 1-3 (AÑO, PATENTE, NUMERO) u Col A PEDIMENTO, obligatorios B–H (VALIDA_TODA),
VALIDACIONES_PEDIMENTO (Tipo I/E, Clave+Régimen+Tipo, Aduana, Patente, IND/CON, Método transporte).
"""
from typing import Dict, Any, Optional, Set, Tuple, List

from ..template_config import (
    AÑO_LEN,
    PATENTE_LEN,
    NUMERO_LEN,
    PEDIMENTO_LEN,
    PEDIMENTO_DASH_POSITIONS,
    parse_pedimento_col_a,
    _cell_to_str,
)
from ..common.common_validators import (
    check_required_max,
    check_in_set,
    check_int_in_set,
    check_optional_int_in_set,
    check_optional_short_name,
    check_optional_max_length,
    check_optional_decimal,
    check_optional_in_set,
    parse_date,
    DATE_FORMAT_PREFERENCE_MAP,
)

# --- Desfase
MSG_DESFASE = "Advertencia: Podría existir un desfase en esta línea."
MSG_DESFASE_SOLUCION = "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta."

# --- Col A ---
MSG_PEDIMENTO_VACIO = (
    "Error: (Col. A) La columna de Pedimento está vacío y no se pueden hacer las validaciones. "
    "Capturar en la Columna A un Pedimento nuevo o uno ya existente al cual desee actualizar campos"
)
MSG_PEDIMENTO_LONGITUD = (
    "Error: (Col. A) La columna de Pedimento no cumple con la longitud o sintaxis correcta Ej. (XX-XXXX-XXXXXXX)"
)
MSG_PEDIMENTO_FORMATO = "Error: (Col. A) El Formato del Pedimento: {val} es incorrecto."
MSG_PEDIMENTO_FORMATO_SOLUCION = "Capturar en la columna A el campo Pedimento con este formato ##-####-#######."

# --- Cols 1-3 (AÑO, PATENTE, NUMERO) ---
MSG_ANIO_PATENTE_NUMERO = "Error: (Cols 1-3) AÑO, PATENTE y NUMERO son obligatorios (AÑO 2 dígitos, PATENTE 4, NUMERO 7)."
MSG_PATENTE_NO_EXISTE = "Error: (Patente) La patente {val} no está dada de alta en el catálogo de agentes aduanales."

# --- Obligatorios B–H ---
MSG_OBLIGATORIOS = "Existen campos vacios que son obligatorios, es la {campos}."
MSG_OBLIGATORIOS_SOLUCION = "Revisar la línea del archivo y capturar los campos con la información correcta."

# --- Tipo B ---
MSG_TIPO_INVALIDO = "Error: (Col. B) El Tipo de Operación: {val} no es valido."
MSG_TIPO_SOLUCION = "Capturar una opción valida, que seria I para Importación, E para Exportación."

# --- Combinación B+C+D ---
MSG_COMBINACION = "Error, (Col. B, C y D) La combinación de una operación de {b} con clave de pedimento {c} y régimen {d} no es posible."
MSG_COMBINACION_SOLUCION = "Las posibles combinaciones son {comb}."

# --- Aduana H ---
MSG_ADUANA_NO_EXISTE = "Error: (Col. H) La aduana y sección: {val} no existe."
MSG_ADUANA_SOLUCION = "Revisar que sea correcta esta aduana y seccion de cruce, de ser así actualice los catálogos fijos."

# --- Col J IND/CON ---
MSG_IND_CON_INVALIDO = "Error: (Col. J) La opción de Individual/Consolidado: {val} no es valido."
MSG_IND_CON_SOLUCION = "Capturar una opción valida: IND para Individual, CON para Consolidado: o dejar el campo vacio y automaticamente se asigna Consolidado."

# --- Transporte K/L/M ---
MSG_TRANSP_NO_EXISTE = "Error: (Col. {col}) El Metodo de Transporte: {val} no existe en el Catálogo del Anexo 22 Apendice 3."
MSG_TRANSP_SOLUCION = "Revisar si esta asignado Correctamente."

# --- Fechas ---
FECHA_MAX_LEN = 10


def validate_row_desfase_pedimento(
    raw_row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Si la fila tiene al menos 16 columnas y la 16ª (Col N, IEPS/desfase) tiene valor, error de desfase. Orden: AÑO,PATENTE,NUMERO,TIPO,...,IEPS en índice 15."""
    values_ordered = list(raw_row.values()) if raw_row else []
    desfase_idx = 15  # IEPS en PEDIMENTOS_TEMPLATE_ORDER (tras AÑO,PATENTE,NUMERO + 12 columnas más)
    if len(values_ordered) >= (desfase_idx + 1):
        cell = _cell_to_str(values_ordered[desfase_idx])
        if cell.strip():
            return {
                "line": line_num,
                "col": "",
                "msg": f"{MSG_DESFASE} {MSG_DESFASE_SOLUCION}",
                "warning": True,
            }
    return None


def validate_row_pedimento_required_col_a(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Col A (PEDIMENTO) obligatorio y longitud 15."""
    val = (row.get("PEDIMENTO") or "").strip()
    if not val:
        return {"line": line_num, "col": "PEDIMENTO", "msg": MSG_PEDIMENTO_VACIO}
    if len(val) != PEDIMENTO_LEN:
        return {
            "line": line_num,
            "col": "PEDIMENTO",
            "msg": f"{MSG_PEDIMENTO_LONGITUD} {MSG_PEDIMENTO_FORMATO_SOLUCION}",
        }
    return None


def validate_row_pedimento_format(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Formato ##-####-#######: guiones en posiciones 3 y 8 (1-based). Solo aplica cuando la fila trae PEDIMENTO."""
    val = (row.get("PEDIMENTO") or "").strip()
    if not val or len(val) != PEDIMENTO_LEN:
        return None
    if val[PEDIMENTO_DASH_POSITIONS[0]] != "-" or val[PEDIMENTO_DASH_POSITIONS[1]] != "-":
        return {
            "line": line_num,
            "col": "PEDIMENTO",
            "msg": f"{MSG_PEDIMENTO_FORMATO.format(val=val)} {MSG_PEDIMENTO_FORMATO_SOLUCION}",
        }
    return None


def validate_row_pedimento_required_three_cols(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Cols 1-3: AÑO (2 dígitos), PATENTE (4), NUMERO (7) obligatorios y numéricos."""
    cols_missing = []
    año = (row.get("AÑO") or "").strip()
    patente = (row.get("PATENTE") or "").strip()
    numero = (row.get("NUMERO") or "").strip()
    if not año:
        cols_missing.append("Col.1) AÑO")
    elif len(año) > AÑO_LEN or not año.isdigit():
        return {"line": line_num, "col": "AÑO", "msg": "AÑO debe ser 2 dígitos numéricos."}
    if not patente:
        cols_missing.append("Col.2) PATENTE")
    elif len(patente) > PATENTE_LEN or not patente.isdigit():
        return {"line": line_num, "col": "PATENTE", "msg": "PATENTE debe ser 4 dígitos numéricos."}
    if not numero:
        cols_missing.append("Col.3) NUMERO")
    elif len(numero) > NUMERO_LEN or not numero.isdigit():
        return {"line": line_num, "col": "NUMERO", "msg": "NUMERO debe ser 7 dígitos numéricos."}
    if cols_missing:
        campos = ", ".join(cols_missing)
        return {
            "line": line_num,
            "col": "",
            "msg": f"{MSG_ANIO_PATENTE_NUMERO} Faltan: {campos}.",
        }
    return None


def validate_row_patente(
    row: Dict[str, Any],
    line_num: int,
    valid_patentes: Set[str],
) -> Optional[Dict[str, Any]]:
    """Si la fila trae PATENTE o PEDIMENTO (parseado), valida que la patente esté en el catálogo de agentes aduanales."""
    patente_val = (row.get("PATENTE") or "").strip()
    if not patente_val:
        ped = (row.get("PEDIMENTO") or "").strip()
        parsed = parse_pedimento_col_a(ped) if ped else None
        if parsed:
            _, patente_val, _ = parsed
    if not patente_val:
        return None
    if valid_patentes and patente_val not in valid_patentes:
        return {
            "line": line_num,
            "col": "PATENTE",
            "msg": MSG_PATENTE_NO_EXISTE.format(val=patente_val),
        }
    return None


def validate_row_pedimento_required_full(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Obligatorios cuando no es actualizar: TIPO (B), CLAVE_PEDIMENTO (C), REGIMEN (D), FECHA_INICIO (E), FECHA_FINAL (F), FECHA_PAGO/ENTRADA (G), ADUANA_SECCION_CRUCE (H), IND/CON (J)."""
    cols_missing = []
    col_labels = [
        ("TIPO_OPERACION", "Col.B) Tipo Operación"),
        ("CLAVE_PEDIMENTO", "Col.C) Clave Pedimento"),
        ("REGIMEN", "Col.D) Clave Régimen"),
        ("FECHA_INICIO", "Col.E) Fecha Inicio"),
        ("FECHA_FINAL", "Col.F) Fecha Final"),
        ("FECHA_PAGO", "Col.G) Fecha de Entrada/Referencia"),
        ("ADUANA_SECCION_CRUCE", "Col.H) Aduana y Sección de Cruce"),
        ("INDIVIDUAL_CONSOLIDADO", "Col.J) Tipo de Pedimento (IND/CON)"),
    ]
    for key, label in col_labels:
        if not (row.get(key) or "").strip():
            cols_missing.append(label)
    if not cols_missing:
        return None
    campos = ", ".join(cols_missing)
    return {
        "line": line_num,
        "col": "",
        "msg": f"{MSG_OBLIGATORIOS.format(campos=campos)} {MSG_OBLIGATORIOS_SOLUCION}",
    }


def validate_row_fecha_pedimento(
    row: Dict[str, Any],
    col: str,
    line_num: int,
    date_format_preference: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
    """Valida que el campo de fecha sea válido (longitud ≤10, parseable)."""
    val = (row.get(col) or "").strip()
    if not val:
        return None
    if len(val) > FECHA_MAX_LEN:
        return {
            "line": line_num,
            "col": col,
            "msg": f"Error: ({col}) La fecha supera la longitud de caracteres.",
        }
    if parse_date(val, date_format_preference) is None:
        format_label = (
            DATE_FORMAT_PREFERENCE_MAP.get(date_format_preference, "##/##/####")
            if date_format_preference
            else "##/##/####"
        )
        return {
            "line": line_num,
            "col": col,
            "msg": f"Error: ({col}) La fecha no coincide con el formato ({format_label}).",
        }
    return None


def validate_row_tipo_operacion(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col B: si tiene valor debe ser I o E."""
    val = (row.get("TIPO_OPERACION") or "").strip().upper()
    if not val:
        return None
    if val not in ("I", "E"):
        return {
            "line": line_num,
            "col": "TIPO_OPERACION",
            "msg": f"{MSG_TIPO_INVALIDO.format(val=row.get('TIPO_OPERACION'))} {MSG_TIPO_SOLUCION}",
        }
    return None


def validate_row_clave_regimen_tipo(
    row: Dict[str, Any],
    line_num: int,
    valid_clave_regimen_tipo: Set[Tuple[str, str, str]],
) -> Optional[Dict[str, Any]]:
    """Combinación B+C+D debe existir en CodePedimentoRegimen."""
    b = (row.get("TIPO_OPERACION") or "").strip().upper()
    c = (row.get("CLAVE_PEDIMENTO") or "").strip().upper()
    d = (row.get("REGIMEN") or "").strip().upper()
    if not b or not c or not d:
        return None
    key = (c, d, b)
    if valid_clave_regimen_tipo and key not in valid_clave_regimen_tipo:
        # Opcional: listar combinaciones válidas para esa clave
        comb = ", ".join(
            f"operación {t} con clave {cp} y régimen {r}"
            for cp, r, t in valid_clave_regimen_tipo
            if cp == c
        ) or "ninguna para esta clave"
        return {
            "line": line_num,
            "col": "TIPO_OPERACION",
            "msg": f"{MSG_COMBINACION.format(b=b, c=c, d=d)} {MSG_COMBINACION_SOLUCION.format(comb=comb)}",
        }
    return None


def validate_row_aduana(
    row: Dict[str, Any],
    line_num: int,
    valid_aduana_seccion: Set[str],
) -> Optional[Dict[str, Any]]:
    """Col H: si tiene valor debe existir en CustomsSection."""
    val = (row.get("ADUANA_SECCION_CRUCE") or "").strip()
    if not val:
        return None
    # customs_code puede ser 3 caracteres; si Col H trae más, tomar primeros 3
    code = val[:3] if len(val) >= 3 else val
    if valid_aduana_seccion and code not in valid_aduana_seccion:
        return {
            "line": line_num,
            "col": "ADUANA_SECCION_CRUCE",
            "msg": f"{MSG_ADUANA_NO_EXISTE.format(val=val)} {MSG_ADUANA_SOLUCION}",
        }
    return None


def validate_row_ind_con(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col J: si tiene valor debe ser IND o CON."""
    val = (row.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper()
    if not val:
        return None
    if val not in ("IND", "CON"):
        return {
            "line": line_num,
            "col": "INDIVIDUAL_CONSOLIDADO",
            "msg": f"{MSG_IND_CON_INVALIDO.format(val=val)} {MSG_IND_CON_SOLUCION}",
        }
    return None


def validate_row_transporte(
    row: Dict[str, Any],
    line_num: int,
    col_key: str,
    col_label: str,
    valid_anexo22_claves: Set[str],
) -> Optional[Dict[str, Any]]:
    """Valida una columna de método de transporte (K, L o M) contra Anexo 22. Si el conjunto está vacío, no se valida."""
    val = (row.get(col_key) or "").strip().upper()
    if not val:
        return None
    if valid_anexo22_claves and val not in valid_anexo22_claves:
        return {
            "line": line_num,
            "col": col_key,
            "msg": f"{MSG_TRANSP_NO_EXISTE.format(col=col_label, val=val)} {MSG_TRANSP_SOLUCION}",
        }
    return None


def validaciones_pedimento(
    row: Dict[str, Any],
    line_num: int,
    valid_clave_regimen_tipo: Set[Tuple[str, str, str]],
    valid_aduana_seccion: Set[str],
    valid_anexo22_claves: Set[str],
    valid_patentes: Set[str],
    short_name_to_id: Optional[Dict[str, int]] = None,
    date_format_preference: Optional[str] = None,
) -> List[Dict[str, Any]]:
    """Orquestador: formato PEDIMENTO si aplica, tipo B, combinación B+C+D, aduana H, patente, IND/CON J, transporte K/L/M, fechas E/F/G. CLIENTE_SHORT_NAME opcional."""
    errors: List[Dict[str, Any]] = []

    if (row.get("PEDIMENTO") or "").strip():
        err = validate_row_pedimento_format(row, line_num)
        if err:
            errors.append(err)

    # CLIENTE_SHORT_NAME opcional: si viene valor se valida que exista en catálogo.
    if short_name_to_id is not None:
        err = check_optional_short_name(
            row, "CLIENTE_SHORT_NAME", short_name_to_id, line_num
        )
        if err:
            errors.append(err)

    for err in [
        validate_row_patente(row, line_num, valid_patentes),
        validate_row_tipo_operacion(row, line_num),
        validate_row_clave_regimen_tipo(
            row, line_num, valid_clave_regimen_tipo
        ),
        validate_row_aduana(row, line_num, valid_aduana_seccion),
        validate_row_ind_con(row, line_num),
        validate_row_transporte(row, line_num, "MET_TRANSP_ENTRADA", "K", valid_anexo22_claves),
        validate_row_transporte(row, line_num, "MET_TRANSP_ARRIVO", "L", valid_anexo22_claves),
        validate_row_transporte(row, line_num, "MET_TRANSP_SALIDA", "M", valid_anexo22_claves),
    ]:
        if err:
            errors.append(err)

    for col in ("FECHA_INICIO", "FECHA_FINAL", "FECHA_PAGO"):
        err = validate_row_fecha_pedimento(row, col, line_num, date_format_preference)
        if err:
            errors.append(err)

    err = validate_row_fechas_coherencia_clarion(row, line_num, date_format_preference)
    if err:
        errors.append(err)

    return errors


def validate_row_fechas_coherencia_clarion(
    row: Dict[str, Any],
    line_num: int,
    date_format_preference: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
    """
    Reglas legacy Clarion:
      - CON: inicio <= final <= fecha de referencia (pago/entrada).
      - IND: si las fechas difieren, advertencia no bloqueante.
    En este layout CSV la referencia operativa está en FECHA_PAGO (Col G).
    """
    start_raw = (row.get("FECHA_INICIO") or "").strip()
    end_raw = (row.get("FECHA_FINAL") or "").strip()
    ref_raw = (row.get("FECHA_PAGO") or "").strip()
    if not start_raw or not end_raw or not ref_raw:
        return None

    start_dt = parse_date(start_raw, date_format_preference)
    end_dt = parse_date(end_raw, date_format_preference)
    ref_dt = parse_date(ref_raw, date_format_preference)
    if not start_dt or not end_dt or not ref_dt:
        return None

    start_date = start_dt.date()
    end_date = end_dt.date()
    ref_date = ref_dt.date()

    ind_con = (row.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper()
    # Compatibilidad legacy: vacío se trata como consolidado.
    if not ind_con:
        ind_con = "CON"

    if ind_con == "CON":
        if start_date > end_date:
            return {
                "line": line_num,
                "col": "FECHA_FINAL",
                "msg": "Error: La fecha inicio no puede ser mayor que la fecha final.",
            }
        if start_date > ref_date:
            return {
                "line": line_num,
                "col": "FECHA_PAGO",
                "msg": "Error: La fecha inicio no puede ser mayor que la fecha de referencia (Col.G).",
            }
        if end_date > ref_date:
            return {
                "line": line_num,
                "col": "FECHA_PAGO",
                "msg": "Error: La fecha final no puede ser mayor que la fecha de referencia (Col.G).",
            }
        return None

    if ind_con == "IND":
        if not (start_date == end_date == ref_date):
            return {
                "line": line_num,
                "col": "FECHA_INICIO",
                "msg": "Advertencia: En pedimento individual las fechas inicio/final/referencia son distintas.",
                "warning": True,
            }
    return None


# --- Legacy (layout sin PEDIMENTO único): mantener para compatibilidad ---
def validate_row_pedimento_required_legacy(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Requeridos para layout legacy: AÑO, ADUANA (ADUANA_SECCION_CRUCE), PATENTE, NUMERO."""
    for col, max_len in [
        ("AÑO", 2),
        ("ADUANA_SECCION_CRUCE", 3),
        ("PATENTE", 4),
        ("NUMERO", 7),
    ]:
        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 validate_row_pedimento_fk_legacy(
    row: Dict[str, Any],
    line_num: int,
    short_name_to_id: Dict[str, int],
    valid_regimes: Set[str],
    valid_pedimento_codes: Set[str],
) -> Optional[Dict[str, Any]]:
    """FK para layout legacy (CLAVE_PEDIMENTO o CODIGO_PEDIMENTO, REGIMEN). CLIENTE_SHORT_NAME opcional."""
    err = check_optional_short_name(row, "CLIENTE_SHORT_NAME", short_name_to_id, line_num)
    if err:
        return err
    # Template puede dar CLAVE_PEDIMENTO o CODIGO_PEDIMENTO según alias
    code = (row.get("CLAVE_PEDIMENTO") or row.get("CODIGO_PEDIMENTO") or "").strip()
    if not code:
        return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "Requerido"}
    if len(code) > 2:
        return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "Máximo 2 caracteres"}
    if valid_pedimento_codes and code not in valid_pedimento_codes:
        return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "No existe en código pedimento"}
    err = check_in_set(
        row, "REGIMEN", valid_regimes, line_num, max_len=3, catalog_name="régimen"
    )
    if err:
        return err
    return None


def validate_row_pedimento_optionals_legacy(
    row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
    """Opcionales para layout legacy."""
    err = check_optional_max_length(row, "ESTATUS", 30, line_num)
    if err:
        return err
    for col in ("VALOR_USD", "PRECIO_PAGADO", "PESO_BRUTO", "TIPO_CAMBIO"):
        err = check_optional_decimal(row, col, line_num)
        if err:
            return err
    err = check_optional_in_set(
        row, "TIPO_OPERACION", {"imp", "exp"}, line_num, "Debe ser imp o exp"
    )
    if err:
        return err
    err = check_optional_in_set(
        row,
        "TIPO_PEDIMENTO",
        {"normal", "consolidated", "complementary", "automobile"},
        line_num,
        "Tipo no válido",
    )
    if err:
        return err
    return None
