"""
Mapeo fila CSV → datos para ClientProvider, ClientProviderAddress y ClientProviderPrograms.
Paridad Clarion: columnas A–AG a modelos.

El CSV se alimenta en base a las tablas/modelos:
- cp_data: atributos de ClientProvider (clients_and_providers). Claves = nombres de columna del modelo.
- address_data: atributos de ClientProviderAddress (clients_and_providers_address). Se asignan en tasks.
- programs_data: atributos de ClientProviderPrograms (clients_and_providers_programs). Se asignan en tasks.

Las validaciones (validators) aplican reglas de negocio Clarion y respetan longitudes máximas de los modelos.
"""
from datetime import datetime
from decimal import Decimal
from typing import Dict, Any, Optional, Tuple

from api.v1.modules.a76.clients_and_providers.models import ClientOrProviderEnum

from .common_validators import (
    parse_client_or_provider,
    parse_active,
)

MAX_LEN = {
    "rfc": 30,
    "name": 256,
    "short_name": 10,
    "curp": 19,
    "responsible": 80,
    "position": 30,
    "incoterm": 19,
    "email": 100,
    "phone": 30,
    "address": 100,
    "postal_code": 15,
    "city": 30,
    "state": 30,
    "country": 3,
    "contact": 50,
    "extra_information": 399,
    "web_key": 40,
    "program": 7,
    "program_number": 40,
    "prosec_authorization": 20,
    "secon_authorization": 20,
    "manufacturer_id": 25,
    "broker": 6,
    "import_broker": 6,
    "transfer_key": 8,
    "certified_company_registry": 40,
    "neighborhood": 40,
    "exterior_number": 20,
    "fax": 30,
}


def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]:
    if val is None:
        return None
    s = str(val).strip()
    if not s:
        return None
    if max_len and len(s) > max_len:
        return s[:max_len]
    return s


def _parse_date_to_yyyymmdd(val: Any) -> Optional[int]:
    """Convierte fecha dd/mm/yyyy o similar a entero YYYYMMDD para secon_auth_date."""
    if not val or not str(val).strip():
        return None
    s = str(val).strip()
    for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y", "%Y/%m/%d"):
        try:
            d = datetime.strptime(s[:10], fmt)
            return d.year * 10000 + d.month * 100 + d.day
        except ValueError:
            continue
    return None


def row_to_client_provider_data(
    row_norm: Dict[str, Any], tenant_id: int, company_id: int
) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
    """
    Mapea fila normalizada a datos para ClientProvider, ClientProviderAddress y ClientProviderPrograms.
    Devuelve (cp_data, address_data_or_none, programs_data_or_none).
    Identificador fiscal unificado: solo se guarda en ClientProvider.rfc (RFC o TAX-ID según procedencia).
    Se toma de columna RFC (E); si viene vacía y hay TAX_ID_PROGRAMS (AB), se usa esa para la misma columna rfc.
    """
    rfc_col = _str_or_none(row_norm.get("RFC"), MAX_LEN["rfc"])
    tax_id_programs_col = _str_or_none(row_norm.get("TAX_ID_PROGRAMS"), MAX_LEN["rfc"])
    # Una sola columna: identificador fiscal en cp.rfc (nacional=RFC, extranjero=TAX-ID)
    rfc_unified = rfc_col or tax_id_programs_col
    short_name = _str_or_none(row_norm.get("SHORT_NAME"), MAX_LEN["short_name"])
    if not rfc_unified and not short_name:
        return ({}, None, None)

    client_or_provider = parse_client_or_provider(row_norm.get("TIPO")) or ClientOrProviderEnum.BOTH
    procedencia = _str_or_none(row_norm.get("PROCEDENCIA"), 1)
    if procedencia:
        procedencia = procedencia.upper()[:1]

    # Vinculación 0/1/2 → string
    vinc = (row_norm.get("VINCULACION") or "").strip()
    linking = None
    if vinc in ("0", "1", "2"):
        linking = vinc

    # Transformador/SubMaquila: primera letra T/S/N
    trans = (row_norm.get("TRANSFORMA_SUBMAQ") or "").strip().upper()[:1]
    transform_subassembly = trans if trans in ("T", "S", "N") else None

    cp_data = {
        "tenant_id": tenant_id,
        "company_id": company_id,
        "rfc": rfc_unified or None,
        "name": _str_or_none(row_norm.get("NOMBRE"), MAX_LEN["name"]),
        "short_name": short_name,
        "curp": _str_or_none(row_norm.get("CURP"), MAX_LEN["curp"]),
        "client_or_provider": client_or_provider,
        "type_nat_foreign": procedencia,
        "linking": linking,
        "transform_subassembly": transform_subassembly,
        "extra_information": _str_or_none(row_norm.get("INFORMACION_EXTRA"), MAX_LEN["extra_information"]),
        "web_key": _str_or_none(row_norm.get("CLAVE_WEB"), MAX_LEN["web_key"]),
        "responsible": _str_or_none(row_norm.get("RESPONSABLE"), MAX_LEN["responsible"]),
        "position": _str_or_none(row_norm.get("POSICION"), MAX_LEN["position"]),
        "incoterm": _str_or_none(row_norm.get("INCOTERM"), MAX_LEN["incoterm"]),
        "is_active": parse_active(row_norm.get("ACTIVO")),
    }

    # Address
    email = _str_or_none(row_norm.get("EMAIL"), MAX_LEN["email"])
    phone = _str_or_none(row_norm.get("TELEFONO"), MAX_LEN["phone"])
    address_str = _str_or_none(row_norm.get("DIRECCION"), MAX_LEN["address"])
    address_data = None
    if email or phone or address_str or _str_or_none(row_norm.get("CODIGO POSTAL")) or _str_or_none(row_norm.get("COLONIA")) or _str_or_none(row_norm.get("NUM_EXT")):
        address_data = {
            "tenant_id": tenant_id,
            "company_id": company_id,
            "streets": address_str,
            "exterior_number": _str_or_none(row_norm.get("NUM_EXT"), MAX_LEN["exterior_number"]),
            "neighborhood": _str_or_none(row_norm.get("COLONIA"), MAX_LEN["neighborhood"]),
            "postal_code": _str_or_none(row_norm.get("CODIGO POSTAL"), MAX_LEN["postal_code"]),
            "city": _str_or_none(row_norm.get("CIUDAD"), MAX_LEN["city"]),
            "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]),
            "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"]),
            "phone": phone,
            "fax_number": _str_or_none(row_norm.get("FAX"), MAX_LEN["fax"]),
            "email": email,
            "contact": _str_or_none(row_norm.get("CONTACTO"), MAX_LEN["contact"]),
        }

    # Programs (SECON, PROSEC, empresa certificada, etc.)
    tipo_prog = _str_or_none(row_norm.get("TIPO_PROGRAMA_SECON"), MAX_LEN["program"])
    if tipo_prog and tipo_prog.lower() == "ninguno":
        tipo_prog = None
    num_prog = _str_or_none(row_norm.get("NUM_PROGRAMA_SECON"), MAX_LEN["program_number"])
    secon_auth = _str_or_none(row_norm.get("NUM_PROGRAMA_SECON"), MAX_LEN["secon_authorization"])
    fecha_secon = _parse_date_to_yyyymmdd(row_norm.get("FECHA_AUT_SECON"))
    es_prosec = (row_norm.get("ES_PROSEC") or "").strip().upper()
    prosec_val = "1" if es_prosec == "SI" else ("0" if es_prosec else None)
    num_aut_prosec = _str_or_none(row_norm.get("NUM_AUT_PROSEC"), MAX_LEN["prosec_authorization"])
    es_cert = (row_norm.get("ES_EMPRESA_CERTIFICADA") or "").strip().upper()
    is_certified = "S" if es_cert == "SI" else ("N" if es_cert else None)
    reg_cert = _str_or_none(row_norm.get("REGISTRO_EMPRESA_CERT"), MAX_LEN["certified_company_registry"])
    vinc_prop = row_norm.get("VINCULACION")
    applied_proportion = None
    if vinc_prop is not None and str(vinc_prop).strip() in ("0", "1", "2"):
        try:
            applied_proportion = Decimal(str(vinc_prop).strip())
        except Exception:
            pass

    programs_data = None
    if (
        tipo_prog or num_prog or fecha_secon is not None or prosec_val or num_aut_prosec
        or is_certified or reg_cert
        or _str_or_none(row_norm.get("MANUFACTURER_ID"))
        or _str_or_none(row_norm.get("BROKER_EXPO"))
        or _str_or_none(row_norm.get("BROKER_IMPO"))
        or _str_or_none(row_norm.get("CLAVE_TRANSFER"))
        or applied_proportion is not None
    ):
        # Identificador fiscal solo en ClientProvider.rfc (columna única)
        programs_data = {
            "program": tipo_prog[:7] if tipo_prog else None,
            "program_number": num_prog,
            "secon_authorization": secon_auth,
            "secon_auth_date": fecha_secon,
            "prosec": prosec_val,
            "prosec_authorization": num_aut_prosec,
            "is_certified_company": is_certified,
            "certified_company_registry": reg_cert,
            "manufacturer_id": _str_or_none(row_norm.get("MANUFACTURER_ID"), MAX_LEN["manufacturer_id"]),
            "broker": _str_or_none(row_norm.get("BROKER_EXPO"), MAX_LEN["broker"]),
            "import_broker": _str_or_none(row_norm.get("BROKER_IMPO"), MAX_LEN["import_broker"]),
            "transfer_key": _str_or_none(row_norm.get("CLAVE_TRANSFER"), MAX_LEN["transfer_key"]),
            "applied_proportion": applied_proportion,
        }

    return (cp_data, address_data, programs_data)
