"""
Validaciones CSV para Encabezados de Facturas de Exportación (Expo Def) y Cambio de Régimen.
Paridad Clarion: VALIDA_TODA_FAC_EXPO, VALIDA_PARCIAL_FAC_EXPO, VALIDACIONES_FAC_EXPO.
Estructura CSV: PEDIMENTO (A), REMESA (B), NUMERO FACTURA (C), ... MANIFIESTO (Y), E-DOCUMENT (Z),
NUM. OPERACION (AA), ENVIADO POR (AB), ADUANA DE CRUCE (AC), OBSERVACIONES E/I, FACTURA ALTERNA.
"""
from datetime import datetime
from typing import Any, Dict, List, Optional, Set
from .pedimento_resolution import resolve_pedimento_candidates

from .encabezados_impo_temp import (
    _clip,
    _err,
    _get,
    _parse_int,
    _pedimento_key_from_parsed,
    _patente_from_agente_aduanal,
    _validaciones_catalogos,
    _validaciones_factura_longitud,
    _validaciones_moneda,
    _validaciones_tipo_cambio,
    _validaciones_tipo_peso,
    _validaciones_transporte,
)
from .encabezados_impo_def import (
    parse_pedimento_col_a_impo_def,
)

# Expo: pedimento mismo formato ##-####-####### (15 chars)
MAX_LEN_PEDIMENTO_EXPO = 15
MAX_LEN_FACTURA = 15

# Regímenes: Exportación (sin cambio de régimen) vs Cambio de Régimen (IMD)
REGIMENES_EXPO = frozenset({"EXD", "ETE", "ETR"})
REGIMEN_IMD = "IMD"
CLAVES_PEDIMENTO_CAMBIO_REGIMEN = frozenset({"F5", "A3"})


def _validaciones_obligatorios_toda_expo(
    row: Dict[str, Any],
    line_num: int,
    tiene_pedimento: bool,
) -> Optional[Dict[str, Any]]:
    """Obligatorios VALIDA_TODA para Expo: C, D, F, G, H, I, J; AC (Aduana de Cruce) si hay pedimento."""
    obligatorios: List[str] = []
    if not _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA"):
        obligatorios.append("(Col.C) Número de Factura")
    if not _get(row, "FECHA FACTURA", "FECHA"):
        obligatorios.append("(Col.D) Fecha de la Factura")
    if not _get(row, "REGIMEN", "CLAVEDOCUMENTO"):
        obligatorios.append("(Col.F) Régimen")
    if not _get(row, "CLAVE PROVEEDOR"):
        obligatorios.append("(Col.G) Clave del Proveedor")
    if not _get(row, "CLAVE VENDIDO A"):
        obligatorios.append("(Col.H) Clave del Vendido A")
    if not _get(row, "CLAVE ENVIADO A"):
        obligatorios.append("(Col.I) Clave del Enviado A")
    if not _get(row, "AGENTE ADUANAL"):
        obligatorios.append("(Col.J) Clave del Agente Aduanal")
    if tiene_pedimento and not _get(row, "ADUANA DE CRUCE"):
        obligatorios.append("(Col.AC) Aduana de Cruce")
    if obligatorios:
        return _err(
            line_num,
            "ARCHIVO CSV",
            f"Existen campos vacíos que son obligatorios: {', '.join(obligatorios)}. Revisar para Exportación.",
        )
    return None


def _validaciones_regimen_expo(
    row: Dict[str, Any],
    line_num: int,
    cambio_regimen: bool,
) -> Optional[Dict[str, Any]]:
    """Col F: si cambio_regimen → IMD; si no → EXD, ETR, ETE."""
    f = _get(row, "REGIMEN", "CLAVEDOCUMENTO")
    if not f:
        return None
    f_upper = f.upper()
    if cambio_regimen:
        if f_upper != REGIMEN_IMD:
            return _err(
                line_num,
                "REGIMEN",
                f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido para este tipo de movimiento. "
                "Los válidos para Cambio de Régimen son: IMD.",
            )
    else:
        if f_upper not in REGIMENES_EXPO:
            return _err(
                line_num,
                "REGIMEN",
                f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido para este tipo de movimiento. "
                "Los válidos para Exportación son: EXD, ETE y ETR.",
            )
    return None


def _validaciones_pedimento_remesa_expo(
    row: Dict[str, Any],
    line_num: int,
    cambio_regimen: bool,
    pedimento_rows: List[Dict[str, Any]],
    remesa_por_pedimento_bd: Dict[str, Set[int]],
    remesa_por_pedimento_csv: Dict[str, Dict[int, str]],
    autonumerar_remesas: bool,
    invoice_date_parsed: Optional[datetime],
    recalcular_fecha_pedimentos: bool = False,
) -> Optional[Dict[str, Any]]:
    """Pedimento Col A: formato ##-####-#######. Si cambio_regimen: tipo I, régimen IMD, ClavePed F5/A3. Si no: tipo E, régimen EXD/ETE/ETR. Remesa igual que imp_def."""
    col_a = _get(row, "PEDIMENTO")
    col_b_raw = row.get("REMESA")
    col_b = _clip(col_b_raw)
    col_f = _get(row, "REGIMEN", "CLAVEDOCUMENTO").upper()

    if not col_a:
        if col_b:
            return _err(
                line_num,
                "REMESA",
                f"Error: (Celda B{line_num}) Está asignado el número de Remesa y no se tiene un pedimento en (Celda A{line_num}).",
            )
        return None

    if len(col_a) > MAX_LEN_PEDIMENTO_EXPO:
        return _err(
            line_num,
            "PEDIMENTO",
            f"Error: (Celda A{line_num}) El Pedimento: {col_a} supera la longitud de caracteres. Use formato ##-####-#######.",
        )
    parsed = parse_pedimento_col_a_impo_def(col_a)
    if not parsed:
        return _err(
            line_num,
            "PEDIMENTO",
            f"Error: (Celda A{line_num}) El Formato del Pedimento: {col_a} es incorrecto. Use ##-####-#######.",
        )

    customs_office, license_val, pedimento_number = parsed
    key = _pedimento_key_from_parsed(customs_office, license_val, pedimento_number)
    patente_lookup = _patente_from_agente_aduanal(row, license_val)
    ped_resolved = resolve_pedimento_candidates(
        customs_office=customs_office,
        license_val=license_val,
        pedimento_number=pedimento_number,
        patente_lookup=patente_lookup,
        pedimento_rows=pedimento_rows,
    )
    if ped_resolved["status"] == "not_found":
        return _err(
            line_num,
            "PEDIMENTO",
            f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} no existe en el Catálogo de Pedimentos. "
            + (
                "Darlo de alta como pedimento de Importación Definitiva (Cambio de Régimen)."
                if cambio_regimen
                else "Darlo de alta como pedimento de Exportación."
            ),
        )
    if ped_resolved["status"] == "ambiguous":
        return _err(
            line_num,
            "PEDIMENTO",
            f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} coincide con múltiples registros. "
            "Valide aduana/patente para identificar un único pedimento.",
        )

    ped_info = ped_resolved["pedimento"]
    op_type = (ped_info.get("operation_type") or "").strip().upper()
    regimen_ped = (ped_info.get("regime") or "").strip().upper()
    pedimento_code = (ped_info.get("pedimento_code") or "").strip().upper()

    if cambio_regimen:
        if op_type != "IMP" and op_type != "I":
            return _err(
                line_num,
                "PEDIMENTO",
                f"Error: (Celda A{line_num}) Este Número de Pedimento: {col_a} no está marcado como Importación Definitiva.",
            )
        if regimen_ped != REGIMEN_IMD:
            return _err(
                line_num,
                "PEDIMENTO",
                f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido para Cambio de Régimen. Válidos: IMD.",
            )
        if col_f and col_f != REGIMEN_IMD:
            return _err(
                line_num,
                "REGIMEN",
                f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} debe ser IMD para Cambio de Régimen.",
            )
        if pedimento_code and pedimento_code not in CLAVES_PEDIMENTO_CAMBIO_REGIMEN:
            return _err(
                line_num,
                "PEDIMENTO",
                f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene la clave {pedimento_code}, no definida para Cambio de Régimen/Regularización. Use F5 o A3.",
            )
    else:
        if op_type != "EXP" and op_type != "E":
            return _err(
                line_num,
                "PEDIMENTO",
                f"Error: (Celda A{line_num}) Este Número de Pedimento: {col_a} no está marcado como Exportación.",
            )
        if regimen_ped not in REGIMENES_EXPO:
            return _err(
                line_num,
                "PEDIMENTO",
                f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido para Exportación. Válidos: EXD, ETE, ETR.",
            )
        if col_f and col_f != regimen_ped:
            return _err(
                line_num,
                "REGIMEN",
                f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} capturado es diferente al del Pedimento: {regimen_ped}.",
            )

    # Rango de fechas si pedimento consolidado (omitir si recalcular_fecha_pedimentos = True, paridad Clarion)
    if not recalcular_fecha_pedimentos:
        pedimento_type = (ped_info.get("pedimento_type") or "").strip().lower()
        if pedimento_type == "consolidated" and invoice_date_parsed and ped_info.get("entry_date") and ped_info.get("end_date"):
            entry = ped_info["entry_date"]
            end = ped_info["end_date"]
            if hasattr(entry, "date"):
                entry = entry.date()
            if hasattr(end, "date"):
                end = end.date()
            inv_d = invoice_date_parsed.date() if hasattr(invoice_date_parsed, "date") else invoice_date_parsed
            if inv_d < entry or inv_d > end:
                return _err(
                    line_num,
                    "FECHA FACTURA",
                    f"Error: (Celda D{line_num} y A{line_num}) La Fecha de la Factura no corresponde al rango de fechas del Pedimento {col_a}.",
                )

    if not autonumerar_remesas and not col_b:
        return _err(
            line_num,
            "REMESA",
            f"Error: (Celda B{line_num}) El Número de Remesa está vacío y se tiene un Pedimento en la Celda A{line_num}.",
        )
    remesa_int = _parse_int(col_b_raw)
    if col_b and remesa_int is not None and remesa_int == 0:
        return _err(
            line_num,
            "REMESA",
            f"Error: (Celda B{line_num}) El Número de Remesa no puede ser 0.",
        )

    factura_actual = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA")
    if remesa_int is not None and key in remesa_por_pedimento_csv:
        other = remesa_por_pedimento_csv[key].get(remesa_int)
        if other and other != factura_actual:
            return _err(
                line_num,
                "REMESA",
                f"Error: (Celda B{line_num}) El Número de Remesa ya está asignado a la factura {other} en este archivo CSV.",
            )
    return None


def _validaciones_manifiesto(
    row: Dict[str, Any],
    line_num: int,
    valid_manifiesto_codes: Set[str],
) -> Optional[Dict[str, Any]]:
    """Col Y MANIFIESTO: si viene informado, debe existir en catálogo."""
    y = _get(row, "MANIFIESTO")
    if not y:
        return None
    if valid_manifiesto_codes and y.strip() not in valid_manifiesto_codes:
        return _err(
            line_num,
            "MANIFIESTO",
            f"Error: (Celda Y{line_num}) El Número de Manifiesto: {y} no está dado de alta en el Catálogo de Manifiestos.",
        )
    return None


def _validaciones_enviado_por(
    row: Dict[str, Any],
    line_num: int,
    valid_enviado_por_ids: Set[int],
    valid_enviado_por_short_names: Set[str],
) -> Optional[Dict[str, Any]]:
    """Col AB ENVIADO POR: Cliente/Proveedor o equivalente."""
    ab = row.get("ENVIADO POR")
    if ab is None or str(ab).strip() == "":
        return None
    v = _parse_int(ab)
    if v is not None:
        if valid_enviado_por_ids and v not in valid_enviado_por_ids:
            return _err(
                line_num,
                "ENVIADO POR",
                f"Error: (Celda AB{line_num}) La Clave del Enviado Por: {ab} no existe en el Catálogo de Clientes/Proveedores o Equivalentes.",
            )
        return None
    sn_norm = str(ab).strip().upper()
    if valid_enviado_por_short_names and sn_norm not in valid_enviado_por_short_names:
        return _err(
            line_num,
            "ENVIADO POR",
            f"Error: (Celda AB{line_num}) La Clave del Enviado Por: {ab} no existe en el Catálogo de Clientes/Proveedores o Equivalentes.",
        )
    return None


def _normalize_tipo_transporte_ferro(row: Dict[str, Any]) -> Dict[str, Any]:
    """Clarion acepta 'FERRO BARCAZA'. Normaliza a FERROBARCAZA para reutilizar validación TEM."""
    out = dict(row)
    m = out.get("TIPO TRANSPORTE")
    if m is not None and str(m).strip().upper().replace(" ", "") == "FERROBARCAZA":
        out["TIPO TRANSPORTE"] = "FERROBARCAZA"
    return out


def validate_row_encabezados_expo(
    row: Dict[str, Any],
    line_num: int,
    actualizar: bool,
    cambio_regimen: bool,
    tipo_factura: str,
    invoice_exists_by_number: Dict[str, bool],
    invoice_processed_by_number: Dict[str, bool],
    remesa_por_pedimento_bd: Dict[str, Set[int]],
    remesa_por_pedimento_csv: Dict[str, Dict[int, str]],
    valid_provider_ids: Set[int],
    valid_sold_to_ids: Set[int],
    valid_shipped_to_ids: Set[int],
    valid_broker_ids: Set[int],
    valid_transporter_keys: Set[str],
    valid_incoterms: Set[str],
    valid_aduana_codes: Set[str],
    valid_currency_codes: Set[str],
    invoice_in_report_by_number: Optional[Dict[str, bool]] = None,
    pedimento_rows: Optional[List[Dict[str, Any]]] = None,
    valid_provider_short_names: Optional[Set[str]] = None,
    valid_sold_to_short_names: Optional[Set[str]] = None,
    valid_shipped_to_short_names: Optional[Set[str]] = None,
    valid_broker_claves: Optional[Set[str]] = None,
    valid_manifiesto_codes: Optional[Set[str]] = None,
    valid_enviado_por_ids: Optional[Set[int]] = None,
    valid_enviado_por_short_names: Optional[Set[str]] = None,
    exchange_rate_by_date: Optional[Dict[str, Any]] = None,
    invoice_has_partidas_by_number: Optional[Dict[str, bool]] = None,
    existing_tipo_moneda_by_number: Optional[Dict[str, str]] = None,
    autonumerar_remesas: bool = False,
    recalcular_fecha_pedimentos: bool = False,
    date_format: Optional[str] = None,
    parse_date_fn=None,
    warnings: Optional[List[Dict[str, Any]]] = None,
) -> List[Dict[str, Any]]:
    """
    Valida una fila de CSV de Encabezados de Exportación (Expo Def) o Cambio de Régimen.
    Clarion: VALIDA_TODA_FAC_EXPO vs VALIDA_PARCIAL_FAC_EXPO.
    """
    errors: List[Dict[str, Any]] = []

    factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA")
    if not factura:
        errors.append(_err(
            line_num,
            "NUMERO FACTURA",
            "Error: (Col.C) La columna de Número de Factura está vacía y no se pueden hacer las validaciones.",
        ))
        return errors

    if invoice_processed_by_number.get(factura.strip(), False):
        errors.append(_err(
            line_num,
            "NUMERO FACTURA",
            f"Error: (Celda C{line_num}) El Número de Factura: {factura} ya existe y está Actualizada, no se puede hacer cambios.",
        ))
        return errors

    if actualizar and factura.strip() not in invoice_exists_by_number:
        errors.append(_err(
            line_num,
            "NUMERO FACTURA",
            "Error: (Col.C) Factura de Exportación no existe (modo Actualizar).",
        ))
        return errors

    if actualizar and (invoice_in_report_by_number or {}).get(factura.strip(), False):
        errors.append(_err(
            line_num,
            "NUMERO FACTURA",
            "Error: (Col.C) Factura de Exportación Rep. La factura está en reporte y no se puede actualizar.",
        ))
        return errors

    use_partial = actualizar and invoice_exists_by_number.get(factura.strip(), False)
    tiene_pedimento = bool(_get(row, "PEDIMENTO"))

    if not use_partial:
        err = _validaciones_obligatorios_toda_expo(row, line_num, tiene_pedimento)
        if err:
            errors.append(err)

    invoice_date_parsed = None
    if parse_date_fn:
        date_str = _get(row, "FECHA FACTURA", "FECHA")
        if date_str:
            invoice_date_parsed = parse_date_fn(date_str, date_format)

    row_transport = _normalize_tipo_transporte_ferro(row)
    has_partidas = invoice_has_partidas_by_number.get(factura.strip(), False) if invoice_has_partidas_by_number else False
    existing_moneda = existing_tipo_moneda_by_number.get(factura.strip()) if existing_tipo_moneda_by_number else None

    for err in [
        _validaciones_pedimento_remesa_expo(
            row,
            line_num,
            cambio_regimen,
            pedimento_rows or [],
            remesa_por_pedimento_bd,
            remesa_por_pedimento_csv,
            autonumerar_remesas,
            invoice_date_parsed,
            recalcular_fecha_pedimentos,
        ),
        _validaciones_factura_longitud(row, line_num),
        _validaciones_regimen_expo(row, line_num, cambio_regimen),
        _validaciones_transporte(row_transport, line_num),
        _validaciones_moneda(
            row,
            line_num,
            valid_currency_codes or set(),
            has_partidas if use_partial else None,
            existing_moneda if use_partial else None,
        ),
        _validaciones_tipo_peso(row, line_num),
        _validaciones_catalogos(
            row,
            line_num,
            valid_provider_ids or set(),
            valid_sold_to_ids or set(),
            valid_shipped_to_ids or set(),
            valid_provider_short_names or set(),
            valid_sold_to_short_names or set(),
            valid_shipped_to_short_names or set(),
            valid_broker_ids or set(),
            valid_broker_claves or set(),
            valid_transporter_keys or set(),
            valid_incoterms or set(),
            valid_aduana_codes or set(),
        ),
        _validaciones_manifiesto(row, line_num, valid_manifiesto_codes or set()),
        _validaciones_enviado_por(
            row,
            line_num,
            valid_enviado_por_ids or set(),
            valid_enviado_por_short_names or set(),
        ),
        _validaciones_tipo_cambio(
            row, line_num, invoice_date_parsed, exchange_rate_by_date or {}, warnings
        ),
    ]:
        if err:
            errors.append(err)

    return errors
