"""
Validaciones comunes de fila para import CSV de clases de materiales.
Paridad con Clarion: VALIDACIONES_CLASE (Col A len, Col D/E/F/G en catálogo, Col F len≥8, Col H ≤100, Col J en catálogo CP).
"""
from typing import Dict, Any, Optional, Set, List

from ..common.common_validators import (
    check_max_length,
    check_int_range,
    check_decimal_max,
)

MSG_CLASE_VACIO = "Error: (Col. A) La columna de Clase esta vacio y no se pueden hacer las validaciones."
MSG_CLASE_VACIO_SOLUCION = "Capturar en la Columna A una Clase nueva o una ya existente a la cual desee actualizar campos"


def validate_row_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """CLASE obligatorio; mensaje Clarion si está vacío. Acepta clave 'CLASE' o 'CLAVE CLASE' (por si el CSV no se normalizó)."""
    val = (row.get("CLASE") or row.get("CLAVE CLASE") or "").strip()
    if not val:
        return {"line": line_num, "col": "CLASE", "msg": f"{MSG_CLASE_VACIO} {MSG_CLASE_VACIO_SOLUCION}"}
    if len(val) > 8:
        return {
            "line": line_num,
            "col": "CLASE",
            "msg": f"Error: (Col. A) La Clase: {val} supera la longitud de caracteres. Capturar en la columna A una Clase de 8 caracteres como máximo.",
        }
    return None


def validate_row_required_full(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Obligatorios en validación completa: B, D, E, F (Clarion VALIDA_TODA_CLASE)."""
    cols_labels = [
        ("DESCRIPCIONE", "(Col.B) Descripción Español"),
        ("CLAVEMAT", "(Col.D) Tipo de Material"),
        ("UNIMED", "(Col.E) U.M. Comercial"),
        ("FRACCION", "(Col.F) Fraccion Arancelaria Mex."),
    ]
    missing = [(col, label) for col, label in cols_labels if not (row.get(col) or "").strip()]
    if not missing:
        return None
    campos = ", ".join(label for _, label in missing)
    first_col = missing[0][0]
    return {
        "line": line_num,
        "col": first_col,
        "msg": f"Existen campos vacios que son obligatorios, es la {campos}. Revisar la línea del archivo y capturar los campos con la información correcta.",
    }


def validate_row_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    checks = [
        ("DESCRIPCIONE", 500),
        ("DESCRIPCIONI", 500),
        ("CLAVEMAT", 10),
        ("UNIMED", 5),
        ("FRACCION", 20),
        ("FRACCIONAME", 16),
        ("CLAVESUB", 5),
        ("FRACCIONEXENTAIVA", 4),
    ]
    for col, max_len in checks:
        err = check_max_length(row, col, max_len, line_num)
        if err:
            return err
    return None


def validate_row_fraction_min(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """FRACCION (Col F): si no vacío, mínimo 8 caracteres (Clarion VALIDACIONES_CLASE)."""
    val = (row.get("FRACCION") or "").strip()
    if not val:
        return None
    if len(val) < 8:
        return {
            "line": line_num,
            "col": "FRACCION",
            "msg": f"La Fraccion {val} no alcanza la longitud de 8 caracteres. Capturar en la columna F una Fracción de 8 caracteres.",
        }
    return None


def validate_row_types(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    return check_int_range(row, "REVFISICA", line_num, -32768, 32767)


def validate_row_fks(
    row: Dict[str, Any],
    line_num: int,
    valid_material_keys: Optional[Set[str]],
    valid_uom_codes: Optional[Set[str]],
) -> Optional[Dict[str, Any]]:
    val_d = (row.get("CLAVEMAT") or "").strip()
    if val_d and valid_material_keys is not None and val_d not in valid_material_keys:
        return {
            "line": line_num,
            "col": "CLAVEMAT",
            "msg": f"Error: (Col. D) El Tipo de Activo Fijo: {val_d} no existe en el Catálogo de Tipos de Activo Fijo. Revisar este Tipo de Activo Fijo en el archivo, en caso de ser correcto actualice los catálogos fijos.",
        }
    val_e = (row.get("UNIMED") or "").strip()
    if val_e and valid_uom_codes is not None and val_e not in valid_uom_codes:
        return {
            "line": line_num,
            "col": "UNIMED",
            "msg": f"Error: (Col. E) La Unidad de Medida Comercial: {val_e} no existe en el Catálogo de U.M. Revisar esta Unidad de Medida en el archivo, en caso de ser correcta actualice los catálogos.",
        }
    return None


def _normalize_fraction_mex_8(value: str) -> str:
    """Primeros 8 caracteres si len>=10, sino hasta 8 (Clarion SUB(ColumnaF, 1, 8))."""
    if not value:
        return ""
    v = value.strip()
    if len(v) >= 10:
        return v[:8]
    return v[:8] if len(v) > 8 else v


def validate_row_fraction_mex_catalog(
    row: Dict[str, Any],
    line_num: int,
    valid_fraction_mex_8: Optional[Set[str]],
) -> Optional[Dict[str, Any]]:
    """Col F: si no vacía, debe existir en catálogo Mex (GFracGenSifra) o Histórico (Clarion)."""
    val = (row.get("FRACCION") or "").strip()
    if not val or valid_fraction_mex_8 is None:
        return None
    code_8 = _normalize_fraction_mex_8(val)
    if not code_8:
        return None
    if code_8 in valid_fraction_mex_8:
        return None
    return {
        "line": line_num,
        "col": "FRACCION",
        "msg": (
            f"Error: (Col. F) La Fraccion Mexicana: {val} no existe en el Catálogo de Fracciones Arancelarias Sifr@ ni en el Historico. "
            "Revisar esta Fracción Arancelaria en el archivo, en caso de ser correcta Actualizar las Fracciones Arancelarias."
        ),
    }


def validate_row_fraction_ame_catalog(
    row: Dict[str, Any],
    line_num: int,
    valid_fraction_ame: Optional[Set[str]] = None,
) -> Optional[Dict[str, Any]]:
    """Col G: si no vacía, debe existir en SITAR fracciones-usa (valid_fraction_ame ignorado; compat)."""
    from api.v1.modules.sitar.fracciones_usa.catalog_resolve import (
        resolve_american_fraction_from_sitar,
    )

    val = (row.get("FRACCIONAME") or "").strip()
    if not val:
        return None
    if resolve_american_fraction_from_sitar(val):
        return None
    return {
        "line": line_num,
        "col": "FRACCIONAME",
        "msg": (
            f"Error: (Col. G) La Fraccion Americana: {val} no existe en el catálogo SITAR (fracciones USA)."
        ),
    }


def validate_row_tasa_depreciacion(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]:
    """Col H: si viene informada, no puede ser mayor al 100 % (Clarion)."""
    val = row.get("TASADEPRECIA")
    if val is None or val == "":
        return None
    err = check_decimal_max(row, "TASADEPRECIA", line_num, 100.0, msg=None)
    if err and "100" in (err.get("msg") or ""):
        v = (row.get("TASADEPRECIA") or "").strip()
        err["msg"] = f"Error: (Col. H) La Tasa de Depreciación: {v} no puede ser mayor al 100 %. Ajustar la Tasa de Depreciacion."
    return err


def validate_row_codigo_producto_cp(
    row: Dict[str, Any],
    line_num: int,
    valid_product_codes_cp: Optional[Set[str]],
    class_code: str = "",
) -> Optional[Dict[str, Any]]:
    """Col J: si no vacía y existe catálogo CP, debe existir en GCodigosProductoCP (Clarion)."""
    val = (row.get("FRACCIONEXENTAIVA") or "").strip()
    if not val:
        return None
    if valid_product_codes_cp is None or len(valid_product_codes_cp) == 0:
        return None
    if val in valid_product_codes_cp:
        return None
    cl = class_code or (row.get("CLASE") or "").strip()
    return {
        "line": line_num,
        "col": "FRACCIONEXENTAIVA",
        "msg": (
            f"Error: (Col. J) La clase: {cl} tiene asignado un código de producto inexistente. "
            "Capturar un código de producto correcto."
        ),
    }


def validaciones_clase(
    row: Dict[str, Any],
    line_num: int,
    valid_material_keys: Optional[Set[str]],
    valid_uom_codes: Optional[Set[str]],
    valid_fraction_mex_8: Optional[Set[str]] = None,
    valid_fraction_ame: Optional[Set[str]] = None,
    valid_product_codes_cp: Optional[Set[str]] = None,
) -> List[Dict[str, Any]]:
    """Reglas compartidas Clarion (VALIDACIONES_CLASE): longitudes, tipos, FKs, fracciones, tasa, código CP."""
    errors: List[Dict[str, Any]] = []

    class_code = (row.get("CLASE") or "").strip()
    for err in [
        validate_row_lengths(row, line_num),
        validate_row_fraction_min(row, line_num),
        validate_row_types(row, line_num),
        validate_row_fks(row, line_num, valid_material_keys, valid_uom_codes),
        validate_row_fraction_mex_catalog(row, line_num, valid_fraction_mex_8),
        validate_row_fraction_ame_catalog(row, line_num, valid_fraction_ame),
        validate_row_tasa_depreciacion(row, line_num),
        validate_row_codigo_producto_cp(
            row, line_num, valid_product_codes_cp, class_code
        ),
    ]:
        if err:
            errors.append(err)

    return errors
