import os
import json
import time
import logging
from datetime import datetime, timedelta
import pymysql
import requests
from dotenv import load_dotenv
import re
import unicodedata
load_dotenv()

DB_HOST = os.getenv('DB_HOST')
DB_USER = os.getenv('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_NAME = os.getenv('DB_NAME')


DB_CONFIG = {
    'host': DB_HOST,
    'user': DB_USER,
    'password': DB_PASSWORD,
    'database': DB_NAME,
    'cursorclass': pymysql.cursors.DictCursor
}




# Datos de la API GCI
GCI_APIKEY = os.getenv("GCI_APIKEY")
GCI_LOGIN_URL = os.getenv("GCI_LOGIN_URL")  # p.e.: "https://api-gci-rest-pruebas.integracionplanok.io/api/login?apikey=..."
# Ajusta tu 'username' y 'password' a lo que necesites
GCI_USERNAME = os.getenv("GCI_USERNAME")  # Ej: 76769507 (sin comillas)
GCI_PASSWORD = os.getenv("GCI_PASSWORD")

# Archivo donde guardaremos el token
TOKEN_CACHE_FILE = "gci_token_cache.json"
PLANOK_LEADS_URL = os.getenv("PLANOK_LEADS_URL", "https://api-gci-rest.integracionplanok.io/api/leads")
FORMULARIO_MEDIO_DEFAULT = os.getenv("FORMULARIO_MEDIO_LLEGADA", "CONILEAD.AI")

def _obtener_nuevo_token():
    if not GCI_USERNAME:
        raise ValueError("No se definió GCI_USERNAME en .env o está vacío.")
    
    payload = {
        "username": int(GCI_USERNAME),  # si tu API requiere int
        "password": GCI_PASSWORD
    }
    response = requests.post(GCI_LOGIN_URL, json=payload, timeout=10)
    response.raise_for_status()
    data = response.json()
    # La API devuelve, por ejemplo, {"token": "...", "usuario": {...}, "idInmobiliaria": 716 }
    bearer_token = data.get("token")
    if not bearer_token:
        raise ValueError("No se encontró 'token' en la respuesta de GCI.")
    return bearer_token

def get_cached_token(force_refresh=False):
    """
    Devuelve un token válido para la API GCI.
    - Si existe un token en el archivo y se guardó hace menos de 20 horas, lo reutiliza.
    - Si no, solicita un token nuevo y lo guarda con la fecha de adquisición.
    """
    # 1) Ver si existe el archivo de caché
    if (not force_refresh) and os.path.exists(TOKEN_CACHE_FILE):
        try:
            with open(TOKEN_CACHE_FILE, "r") as f:
                cache_data = json.load(f)
                token = cache_data.get("token")
                time_fetched_str = cache_data.get("time_fetched")

            if token and time_fetched_str:
                time_fetched = datetime.fromisoformat(time_fetched_str)  # "YYYY-MM-DDTHH:MM:SS..."
                now = datetime.now()
                elapsed = now - time_fetched
                if elapsed < timedelta(hours=20):
                    # Todavía no han pasado 20 horas, reutilizamos
                    logging.info("Reutilizando token GCI desde caché (menos de 20h).")
                    return token
                else:
                    logging.info("Token GCI en caché superó las 20h. Solicitar token nuevo.")
            else:
                logging.info("Archivo de caché mal formado o sin datos, se solicitará un token nuevo.")
        except Exception as e:
            logging.warning(f"Error leyendo caché de token GCI: {e}. Se solicitará un token nuevo.")
    elif not force_refresh:
        logging.info("No existe archivo de caché de token GCI, se solicitará un token nuevo.")
    else:
        logging.info("Forzando refresh de token GCI (force_refresh=True).")

    # 2) Solicitar un token nuevo
    new_token = _obtener_nuevo_token()

    # 3) Guardar en caché
    cache_data = {
        "token": new_token,
        "time_fetched": datetime.now().isoformat()  # Para saber la fecha/hora exacta
    }
    try:
        with open(TOKEN_CACHE_FILE, "w") as f:
            json.dump(cache_data, f)
        logging.info("Nuevo token GCI guardado en caché.")
    except Exception as e:
        logging.warning(f"No se pudo guardar el nuevo token en caché: {e}")

    return new_token

def patch_lead_gci(id_centralizador):
    """
    Envía un PATCH a /api/leads con id_centralizador y fecha_real_contacto = ahora.
    Reutiliza el token de GCI almacenado (si está en vigencia).
    """
    token = get_cached_token()  # 1) Obtiene el token (desde caché si no expiró)
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    fecha_real_contacto = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
    payload = {
        "id_centralizador": id_centralizador,
        "fecha_real_contacto": fecha_real_contacto
    }
    # Haces la petición
    url = "https://api-gci-rest.integracionplanok.io/api/leads" 
    response = requests.patch(url, headers=headers, json=payload)
    response.raise_for_status()
    print("PATCH exitoso", response.text)

def _normalizar_telefono_movil(phone):
    d = re.sub(r"\D", "", str(phone or ""))
    if d.startswith("0056"):
        d = d[4:]
    elif d.startswith("056"):
        d = d[3:]
    if re.fullmatch(r"9\d{8}", d):
        d = "56" + d
    if d and not d.startswith("56"):
        d = "56" + d
    return d

def _normalizar_rut_dni(rut_dni):
    """
    Mantiene formato tipo '16470532-3' cuando viene DV.
    """
    s = str(rut_dni or "").strip().upper()
    s = s.replace(".", "").replace(" ", "")
    s = re.sub(r"[^0-9K\-]", "", s)
    if "-" in s:
        cuerpo, dv = s.split("-", 1)
        cuerpo = re.sub(r"\D", "", cuerpo)
        dv = re.sub(r"[^0-9K]", "", dv)[:1]
        if cuerpo and dv:
            return f"{cuerpo}-{dv}"
        return cuerpo
    return re.sub(r"\D", "", s)

def _sin_tildes(s):
    txt = str(s or "")
    return "".join(ch for ch in unicodedata.normalize("NFD", txt) if unicodedata.category(ch) != "Mn")

def _variantes_medio_llegada(medio_nombre, medio_default):
    candidatos = [
        medio_nombre,
        medio_default,
        "PÁGINA WEB",
        "Página Web",
        "PAGINA WEB",
        "Pagina Web",
        "WEB"
    ]
    out = []
    seen = set()
    for c in candidatos:
        t = str(c or "").strip()
        if not t:
            continue
        if t not in seen:
            out.append(t)
            seen.add(t)
        s2 = _sin_tildes(t)
        if s2 and s2 not in seen:
            out.append(s2)
            seen.add(s2)
    return out

def _variantes_authorization(token):
    t = str(token or "").strip()
    if not t:
        return []
    candidatos = []
    if t.lower().startswith("bearer "):
        raw = t[7:].strip()
        candidatos.extend([t, raw])
    else:
        candidatos.extend([f"Bearer {t}", t])
    out = []
    seen = set()
    for c in candidatos:
        v = str(c or "").strip()
        if v and v not in seen:
            out.append(v)
            seen.add(v)
    return out

def resolver_medio_llegada_por_proyecto(id_proyecto, medio_default=FORMULARIO_MEDIO_DEFAULT):
    """
    Resuelve (id_medio, nombre_medio) para un proyecto:
    - id_medio desde tabla Medio_Proyecto.
    - nombre_medio desde API /proyectos/{id}/mediosDeLlegada.
    """
    medio_id = buscar_id_medio_desde_db(id_proyecto)
    if not medio_id:
        return 0, medio_default

    try:
        token = get_cached_token()
        url = f"https://api-gci-rest.integracionplanok.io/api/proyectos/{id_proyecto}/mediosDeLlegada"
        headers = {"Authorization": f"Bearer {token}"}
        response = requests.get(url, headers=headers, timeout=12)
        if response.status_code == 200:
            data = response.json()
            for medio in data:
                try:
                    if int(medio.get("id", 0)) == int(medio_id):
                        return int(medio_id), (medio.get("nombre") or medio_default)
                except (TypeError, ValueError):
                    continue
        logging.warning(
            f"No se pudo resolver nombre de medio para proyecto {id_proyecto}. status={response.status_code}"
        )
    except Exception as e:
        logging.warning(f"Error resolviendo medio por proyecto {id_proyecto}: {e}")

    return int(medio_id), medio_default

def crear_lead_formulario_planok(
    nombre,
    apellido_paterno,
    telefono_movil,
    email="",
    rut_dni="",
    proyecto_id=17,
    proyecto="",
    comentario="Cotizador en pagina web.",
    utm_source="Conilead",
    utm_medium="",
    utm_campaign="",
    utm_term="",
    utm_content="",
    medio_default=FORMULARIO_MEDIO_DEFAULT,
    planok_leads_url=None
):
    """
    Crea lead en PlanOK usando endpoint POST /api/leads.
    Devuelve un dict con estado de ejecución.
    """
    try:
        proyecto_id = int(proyecto_id or 0)
    except (TypeError, ValueError):
        proyecto_id = 0

    medio_id, medio_nombre = resolver_medio_llegada_por_proyecto(proyecto_id, medio_default=medio_default)
    fecha_ingreso_iso = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
    telefono_norm = _normalizar_telefono_movil(telefono_movil)

    payload = {
        "medio_llegada": medio_nombre or medio_default,
        "fecha_ingreso": fecha_ingreso_iso,
        "rut_dni": _normalizar_rut_dni(rut_dni),
        "nombre": (nombre or "").strip(),
        "apellido_paterno": (apellido_paterno or "").strip(),
        "apellido_materno": "",
        "telefono_fijo": "",
        "telefono_movil": telefono_norm,
        "email": (email or "").strip(),
        "proyecto": (proyecto or "").strip(),
        "proyecto_id": proyecto_id,
        "producto_id": 0,
        "producto_glosa": "",
        "comentario": comentario or "",
        "clausula_publicidad": "",
        "clausula_proyecto": "",
        "utm_source": utm_source or "",
        "utm_medium": utm_medium or "",
        "utm_campaign": utm_campaign or "",
        "utm_term": utm_term or "",
        "utm_content": utm_content or ""
    }

    url = planok_leads_url or PLANOK_LEADS_URL
    if proyecto_id <= 0:
        logging.error("No se envía lead formulario a PlanOK: proyecto_id inválido.")
        return {
            "ok": False,
            "status_code": None,
            "text": "proyecto_id inválido",
            "medio_id": medio_id,
            "medio_llegada": medio_nombre
        }

    try:
        variantes_medio = _variantes_medio_llegada(medio_nombre, medio_default)
        if "CONILEAD.AI" not in variantes_medio:
            variantes_medio.insert(0, "CONILEAD.AI")

        ultimo_response = None
        medio_usado = payload.get("medio_llegada")
        medios_probados = []
        auth_probados = []
        status_code = None
        body = "sin respuesta"

        # Ronda 1: token cacheado. Ronda 2: refresh forzado si hubo 401.
        for ronda in (1, 2):
            token = get_cached_token(force_refresh=(ronda == 2))
            auth_variants = _variantes_authorization(token)
            if not auth_variants:
                raise ValueError("Token GCI vacío para Authorization")

            for auth_idx, auth_value in enumerate(auth_variants, start=1):
                auth_mode = "Bearer" if auth_value.lower().startswith("bearer ") else "Raw"
                if auth_mode not in auth_probados:
                    auth_probados.append(auth_mode)

                headers = {
                    "accept": "application/json",
                    "Content-Type": "application/json",
                    "Authorization": auth_value
                }
                for medio_idx, medio_try in enumerate(variantes_medio, start=1):
                    payload_try = dict(payload)
                    payload_try["medio_llegada"] = medio_try
                    medio_usado = medio_try
                    medios_probados.append(medio_try)
                    response = requests.post(url, json=payload_try, headers=headers, timeout=15)
                    ultimo_response = response
                    status_code = response.status_code
                    body = response.text

                    if response.status_code in (200, 201):
                        logging.info(
                            f"Lead formulario enviado a PlanOK. proyecto_id={proyecto_id}, medio_id={medio_id}, medio='{medio_try}', auth={auth_mode}, ronda={ronda}, intento={auth_idx}.{medio_idx}"
                        )
                        return {
                            "ok": True,
                            "status_code": response.status_code,
                            "text": response.text,
                            "medio_id": medio_id,
                            "medio_llegada": medio_try
                        }

            # Si no fue 401, no vale la pena forzar refresh.
            if status_code != 401:
                break
            if ronda == 1:
                logging.warning("PlanOK /api/leads respondió 401. Se forzará refresh de token y reintento.")

        logging.error(
            "Error PlanOK /api/leads tras reintentos: "
            f"status={status_code}, proyecto_id={proyecto_id}, medio_id={medio_id}, medio_final='{medio_usado}', "
            f"auth_probados={auth_probados}, medios_probados={medios_probados}, telefono_len={len(telefono_norm)}, "
            f"email_present={'yes' if (email or '').strip() else 'no'}, gci_username={GCI_USERNAME}, body={body}"
        )
        return {
            "ok": False,
            "status_code": status_code,
            "text": body,
            "medio_id": medio_id,
            "medio_llegada": medio_usado
        }
    except Exception as e:
        logging.exception(f"Excepcion enviando lead formulario a PlanOK: {e}")
        return {
            "ok": False,
            "status_code": None,
            "text": str(e),
            "medio_id": medio_id,
            "medio_llegada": medio_nombre
        }

def _split_rut_if_needed(identificador_personal, digito_verificador):
    """
    Si el DV no viene (None, '', 'null'), intenta separarlo desde identificador_personal.
    Ejemplos válidos: '12.345.678-9', '12345678-9', '12.345.678-k'
    """
    # DV faltante
    if digito_verificador is None or str(digito_verificador).strip().lower() in ("", "null"):
        s = str(identificador_personal or "").strip()
        # limpiar puntos y espacios
        s = s.replace(".", "").replace(" ", "")
        if "-" in s:
            cuerpo, dv = s.rsplit("-", 1)  # usar el último guion por si acaso
            identificador_personal = cuerpo
            digito_verificador = dv.upper()  # normaliza 'k' -> 'K'
    return identificador_personal, digito_verificador
    
def crear_cliente_natural(identificador_personal, digito_verificador, nombre,
                          apellido_paterno, apellido_materno, celular, email):
    """
    Crea un cliente NATURAL en la API GCI.
    Retorna el 'id' que la API nos devuelve en la respuesta.
    """
    # <<< NUEVO: normalización/separación de RUT >>>
    identificador_personal, digito_verificador = _split_rut_if_needed(
        identificador_personal, digito_verificador
    )

    token = get_cached_token()  # Reutiliza token caché o lo pide si no existe

    url = "https://api-gci-rest.integracionplanok.io/api/clientes/naturales"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {token}"
    }
    payload = {
        "identificadorPersonal": identificador_personal,  # ej. 16470531 (sin puntos ni guion)
        "digitoVerificador": digito_verificador,          # ej. "5"
        "nombre": nombre,
        "apellidoPaterno": apellido_paterno,
        "apellidoMaterno": apellido_materno,
        "celular": celular,
        "email": email
    }

    try:
        response = requests.post(url, headers=headers, json=payload, timeout=10)
        response.raise_for_status()  # Lanza excepción si status >= 400
        data = response.json()
        # La respuesta tiene "id": 27613, ...
        cliente_id = data.get("id")
        if not cliente_id:
            logging.warning(f"No se encontró 'id' en la respuesta: {data}")
        else:
            logging.info(f"Cliente natural creado con ID={cliente_id}")
        return cliente_id
    except requests.exceptions.RequestException as e:
        logging.error(f"Error al crear cliente natural: {e}")
        raise

def buscar_cliente_natural(identificador_personal):
    token = get_cached_token()
    base_url = "https://api-gci-rest.integracionplanok.io/api/clientes/naturales"
    url = f"{base_url}?identificador-personal={identificador_personal}"
    headers = {"Authorization": f"Bearer {token}"}

    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()  # <--- si es 404, lanza HTTPError
        data = response.json()

        # Verificamos si 'data' es dict o lista
        if isinstance(data, dict):
            cliente_id = data.get("id")
            if cliente_id:
                logging.info(f"Cliente natural encontrado con ID={cliente_id}")
                return cliente_id
            else:
                logging.warning(f"No se encontró 'id' en la respuesta: {data}")
                return None

        elif isinstance(data, list):
            if not data:
                logging.warning("Lista vacía. No se encontró cliente.")
                return None
            primer_obj = data[0]
            cliente_id = primer_obj.get("id")
            if cliente_id:
                logging.info(f"Cliente natural encontrado (lista) con ID={cliente_id}")
                return cliente_id
            else:
                logging.warning(f"No se encontró 'id' en el primer elemento: {primer_obj}")
                return None

        else:
            logging.warning(f"Tipo de respuesta no esperado: {type(data)} -> {data}")
            return None

    except requests.exceptions.HTTPError as e:
        # <-- Si GCI devuelve 404 (Not Found), lo interpretamos como "cliente no existe"
        if e.response.status_code == 404:
            logging.info(f"Cliente con identificador {identificador_personal} no existe en GCI.")
            return None
        else:
            logging.error(f"Error al buscar cliente natural: {e}")
            raise  # Re-lanzamos si no es 404

    except requests.exceptions.RequestException as e:
        logging.error(f"Error al buscar cliente natural (otra excepción): {e}")
        raise

def obtener_id_medio_llegada(proyecto_id, nombre_medio):
    """
    Busca el ID de un medio de llegada en un proyecto específico.
    Retorna el ID si lo encuentra, de lo contrario, devuelve None.
    """
    token = get_cached_token()
    url = f"https://api-gci-rest.integracionplanok.io/api/proyectos/{proyecto_id}/mediosDeLlegada"
    
    headers = {
        'Authorization': f'Bearer {token}'
    }

    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        datos = response.json()

        for medio in datos:
            if medio["nombre"].strip().lower() == nombre_medio.strip().lower():
                return medio["id"]  # Devuelve solo el ID

        return None  # Si no se encontró el medio de llegada
    else:
        logging.error(f"Error al consultar la API: {response.status_code}")
        return None

    
    
def _convertir_fecha_a_iso8601(fecha_str):
    """
    Convierte una fecha en formato dd-mm-yyyy a:
    YYYY-MM-DDT00:00:00.000Z
    - Si 'fecha_str' viene vacío o None, retorna None.
    """
    if not fecha_str:
        return None
    try:
        # Parseamos la fecha en formato dd-mm-YYYY
        fecha_obj = datetime.strptime(fecha_str, "%d-%m-%Y")
        # Formateamos con tiempo "00:00:00.000Z"
        fecha_iso = fecha_obj.strftime("%Y-%m-%dT00:00:00.000Z")
        return fecha_iso
    except ValueError as e:
        logging.warning(f"Error al convertir fecha '{fecha_str}': {e}")
        return None

def buscar_id_medio_desde_db(id_proyecto):
    """
    Busca en la tabla Medio_Proyecto el Id_Medio correspondiente al id_proyecto dado.
    Devuelve el Id_Medio si lo encuentra, o None si no hay coincidencias.
    """
    try:
        connection = pymysql.connect(**DB_CONFIG)
        with connection:
            with connection.cursor() as cursor:
                sql = "SELECT Id_Medio FROM Medio_Proyecto WHERE Id_proyecto = %s LIMIT 1"
                cursor.execute(sql, (id_proyecto,))
                resultado = cursor.fetchone()
                if resultado:
                    return resultado["Id_Medio"]
                else:
                    logging.warning(f"No se encontró Id_Medio para el proyecto {id_proyecto}.")
                    return None
    except pymysql.MySQLError as e:
        logging.error(f"Error al consultar la base de datos: {str(e)}")
        return None

def obtener_rut_vendedor_por_proyecto(proyecto_id):
    """
    Busca en la tabla Vendedores el rut_ejecutivo_sin_digito asociado a un proyecto_id.
    Si hay más de uno, devuelve el primero. Si no hay coincidencias, retorna None.
    """
    try:
        connection = pymysql.connect(**DB_CONFIG)
        with connection:
            with connection.cursor() as cursor:
                sql = """
                    SELECT rut_ejecutivo_sin_digito
                    FROM Vendedores
                    WHERE proyecto_id = %s
                    LIMIT 1
                """
                cursor.execute(sql, (proyecto_id,))
                resultado = cursor.fetchone()
                if resultado and resultado.get("rut_ejecutivo_sin_digito"):
                    return str(resultado["rut_ejecutivo_sin_digito"]).strip()
                else:
                    logging.warning(f"No se encontró rut_ejecutivo_sin_digito para proyecto {proyecto_id}.")
                    return None
    except pymysql.MySQLError as e:
        logging.error(f"Error al consultar tabla Vendedores: {e}")
        return None


def crear_seguimiento(
    id_cliente,
    id_proyecto,
    id_medio,
    id_tipo_contacto,
    id_expectativa,
    comentario,
    fecha_recontacto_str,
    mantener_ejecutivo=True,
    rutvendedor=None
):
    """
    Envía un POST a /api/seguimientos con los campos requeridos por PlanOK.
    Incluye hora_recontacto e id_tipo según esquema oficial.
    Si falla el primer intento, se reintenta con id_medio obtenido desde la DB.
    """

    def _enviar(payload):
        logging.info(f"Enviando seguimiento a GCI con payload:\n{json.dumps(payload, ensure_ascii=False, indent=2)}")
        response = requests.post(url, headers=headers, json=payload, timeout=10)
        response.raise_for_status()
        return response.json()

    # Obtener token desde caché
    DEFAULT_ID_EXPECTATIVA = 3
    token = get_cached_token()
    url = "https://api-gci-rest.integracionplanok.io/api/seguimientos"

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {token}"
    }

    # --- Normalizar comentario ---
    if not comentario or str(comentario).strip().lower() in ("", "none", "null"):
        comentario = "Gestión ConiLead, cliente no contestó."

    try:
        id_expectativa = int(id_expectativa) if id_expectativa is not None else 0
    except Exception:
        id_expectativa = 0
    if id_expectativa <= 0:
        logging.warning(f"id_expectativa vacío/0: se usará DEFAULT_ID_EXPECTATIVA={DEFAULT_ID_EXPECTATIVA}")
        id_expectativa = DEFAULT_ID_EXPECTATIVA

    # --- Construcción base del payload ---
    payload = {
        "id_cliente": int(id_cliente or 0),
        "id_proyecto": int(id_proyecto or 0),
        "id_medio": int(id_medio or 0),
        "id_tipo_contacto": int(id_tipo_contacto or 0),
        "id_expectativa": int(id_expectativa),
        "comentario": comentario,
        "fecha_recontacto": fecha_recontacto_str,
        "hora_recontacto": datetime.utcnow().strftime("%H:%M:%S"),  # nuevo
        "mantenerEjecutivo": True,
        "id_tipo": 0  # campo obligatorio aunque sea 0
    }

    try:
        rut_int = int(str(rutvendedor).strip())
        if rut_int > 1000000:
            payload["rut_ejecutivo"] = rut_int
    except (TypeError, ValueError):
        pass  # 👈 No se agrega la clave si no es válido

    try:
        data = _enviar(payload)

    except requests.exceptions.HTTPError as e:
        if hasattr(e, "response") and e.response is not None:
            logging.warning(f"Primer intento fallido: {e} - Respuesta: {e.response.text}")
        else:
            logging.warning(f"Primer intento fallido: {e}")

        # --- Segundo intento con id_medio desde DB ---
        nuevo_id_medio = buscar_id_medio_desde_db(id_proyecto)
        if nuevo_id_medio:
            logging.info(f"Reintentando con nuevo id_medio obtenido desde la DB: {nuevo_id_medio}")
            payload["id_medio"] = int(nuevo_id_medio)
            payload["mantenerEjecutivo"] = True
            try:
                data = _enviar(payload)
            except Exception as e2:
                if hasattr(e2, "response") and e2.response is not None:
                    logging.error(f"Segundo intento también fallido: {e2} - Respuesta: {e2.response.text}")
                else:
                    logging.error(f"Segundo intento también fallido: {e2}")
                return None
        else:
            logging.warning(f"No se pudo obtener nuevo id_medio desde la DB para el proyecto {id_proyecto}")
            return None

    except Exception as e:
        logging.error(f"Error inesperado al crear seguimiento: {e}", exc_info=True)
        return None

    # --- Procesar respuesta ---
    if isinstance(data, dict):
        seguimiento_id = data.get("id") or data.get("id_seguimiento")
        if seguimiento_id:
            logging.info(f"✅ Seguimiento creado con ID={seguimiento_id}")
            return seguimiento_id
        else:
            logging.warning(f"Seguimiento creado, pero sin 'id': {data}")
            return None

    elif isinstance(data, list):
        if not data:
            logging.warning("Respuesta vacía (lista) al crear seguimiento.")
            return None
        primer_elem = data[0]
        if isinstance(primer_elem, int):
            logging.info(f"✅ Seguimiento creado (lista con int) con ID={primer_elem}")
            return primer_elem
        elif isinstance(primer_elem, dict):
            seguimiento_id = primer_elem.get("id")
            if seguimiento_id:
                logging.info(f"✅ Seguimiento creado (array dict) con ID={seguimiento_id}")
                return seguimiento_id
            else:
                logging.warning(f"Elemento sin 'id': {primer_elem}")
                return None
        else:
            logging.warning(f"Elemento inesperado en lista: {primer_elem}")
            return None

    elif isinstance(data, int):
        logging.info(f"✅ La API devolvió un entero como ID de seguimiento: {data}")
        return data

    else:
        logging.warning(f"Respuesta inesperada: {data} (tipo={type(data)})")
        return None
        
def fetch_modelos_por_proyecto(id_proyecto):
    """
    Obtiene los modelos de un proyecto desde la API GCI.
    Inserta/actualiza los registros de ese proyecto en la tabla Modelos.
    Si el usuario no tiene permisos para ese proyecto, lo registra en el log y retorna [].
    """
    token = get_cached_token()
    url = f"https://api-gci-rest.integracionplanok.io/api/proyectos/{id_proyecto}/modelos"
    headers = {"Authorization": f"Bearer {token}"}

    try:
        response = requests.get(url, headers=headers, timeout=20)
        if response.status_code in (401, 403):
            logging.warning(f"No autorizado para proyecto {id_proyecto}: {response.text}")
            return []
        response.raise_for_status()
        data = response.json()

        if isinstance(data, dict) and "message" in data:
            logging.warning(f"Proyecto {id_proyecto} sin permisos: {data['message']}")
            return []

        if not isinstance(data, list):
            logging.warning(f"Respuesta inesperada para proyecto {id_proyecto}: {data}")
            return []

        # Guardar en DB
        connection = pymysql.connect(**DB_CONFIG)
        with connection:
            with connection.cursor() as cursor:
                # 👇 Opción: borrar solo los modelos de ese proyecto
                cursor.execute("DELETE FROM Modelos WHERE id_proyecto = %s", (id_proyecto,))

                sql = """
                INSERT INTO Modelos (
                    id_modelo, id_proyecto, nombre, dormitorios, banos, precioDesde, imagenes, fechaCreacion, fechaEdicion
                ) VALUES (
                    %(id_modelo)s, %(id_proyecto)s, %(nombre)s, %(dormitorios)s, %(banos)s, %(precioDesde)s,
                    %(imagenes)s, %(fechaCreacion)s, %(fechaEdicion)s
                )
                ON DUPLICATE KEY UPDATE
                    nombre = VALUES(nombre),
                    dormitorios = VALUES(dormitorios),
                    banos = VALUES(banos),
                    precioDesde = VALUES(precioDesde),
                    imagenes = VALUES(imagenes),
                    fechaCreacion = VALUES(fechaCreacion),
                    fechaEdicion = VALUES(fechaEdicion);
                """
                for m in data:
                    record = {
                        "id_modelo": m.get("id"),
                        "id_proyecto": id_proyecto,
                        "nombre": m.get("nombre"),
                        "dormitorios": m.get("dormitorios"),
                        "banos": m.get("banos"),
                        "precioDesde": m.get("precioDesde"),
                        "imagenes": json.dumps(m.get("imagenes", []), ensure_ascii=False),
                        "fechaCreacion": m.get("fechaCreacion"),
                        "fechaEdicion": m.get("fechaEdicion"),
                    }
                    cursor.execute(sql, record)
                connection.commit()

        logging.info(f"Modelos insertados/actualizados para proyecto {id_proyecto}: {len(data)}")
        return data

    except Exception as e:
        logging.error(f"Error al obtener modelos de proyecto {id_proyecto}: {e}", exc_info=True)
        return []

def fetch_productos_por_proyecto(id_proyecto):
    """
    Obtiene los productos principales de un proyecto desde la API GCI.
    Solo inserta los que estén disponibles en Web y tengan estado = 'Disponible'.
    Antes de insertar, borra todos los registros previos del proyecto en la tabla Productos.
    """
    token = get_cached_token()
    url = f"https://api-gci-rest.integracionplanok.io/api/proyectos/{id_proyecto}/productos-principales"
    headers = {"Authorization": f"Bearer {token}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        if response.status_code in (401, 403):
            logging.warning(f"No autorizado para proyecto {id_proyecto}: {response.text}")
            return []
        response.raise_for_status()
        data = response.json()

        if isinstance(data, dict) and "message" in data:
            logging.warning(f"Proyecto {id_proyecto} sin permisos: {data['message']}")
            return []

        if not isinstance(data, list):
            logging.warning(f"Respuesta inesperada para proyecto {id_proyecto}: {data}")
            return []

        # Conexión a DB
        connection = pymysql.connect(**DB_CONFIG)
        with connection:
            with connection.cursor() as cursor:
                # Borrar datos previos del proyecto
                cursor.execute("DELETE FROM Productos WHERE id_proyecto = %s", (id_proyecto,))

                sql = """
                INSERT INTO Productos (
                    id_producto, id_proyecto, nombre, nombreProyecto, nombreEtapa, nombreSubAgrupacion,
                    nombreModelo, precio, precioBase, descuentomaximo_valor, descuentomaximo_unidad,
                    descuentocotizadorweb_valor, descuentocotizadorweb_unidad, piso, orientacion,
                    fechaCreacion, fechaEdicion, tipo_producto, programa,
                    superficies_terraza, superficies_interior, superficies_municipal, superficies_util,
                    superficies_total, superficies_loggia, superficies_jardin, superficies_miradorTerraza,
                    superficies_otrasSuperficies, superficies_terreno, disponibleWeb, disponibleSistema,
                    estado, calificacionEnergetica, comentario, cantDormitorios, cantBanos
                ) VALUES (
                    %(id_producto)s, %(id_proyecto)s, %(nombre)s, %(nombreProyecto)s, %(nombreEtapa)s,
                    %(nombreSubAgrupacion)s, %(nombreModelo)s, %(precio)s, %(precioBase)s,
                    %(descuentomaximo_valor)s, %(descuentomaximo_unidad)s,
                    %(descuentocotizadorweb_valor)s, %(descuentocotizadorweb_unidad)s,
                    %(piso)s, %(orientacion)s, %(fechaCreacion)s, %(fechaEdicion)s, %(tipo_producto)s,
                    %(programa)s, %(superficies_terraza)s, %(superficies_interior)s,
                    %(superficies_municipal)s, %(superficies_util)s, %(superficies_total)s,
                    %(superficies_loggia)s, %(superficies_jardin)s, %(superficies_miradorTerraza)s,
                    %(superficies_otrasSuperficies)s, %(superficies_terreno)s, %(disponibleWeb)s,
                    %(disponibleSistema)s, %(estado)s, %(calificacionEnergetica)s, %(comentario)s,
                    %(cantDormitorios)s, %(cantBanos)s
                )
                ON DUPLICATE KEY UPDATE
                    nombre = VALUES(nombre),
                    precio = VALUES(precio),
                    precioBase = VALUES(precioBase),
                    estado = VALUES(estado),
                    disponibleWeb = VALUES(disponibleWeb),
                    disponibleSistema = VALUES(disponibleSistema),
                    fechaEdicion = VALUES(fechaEdicion);
                """

                insertados = 0
                for p in data:
                    if not (p.get("estado") == "Disponible" and p.get("disponibleWeb") is True):
                        continue

                    record = {
                        "id_producto": p.get("id"),
                        "id_proyecto": id_proyecto,
                        "nombre": p.get("nombre"),
                        "nombreProyecto": p.get("nombreProyecto"),
                        "nombreEtapa": p.get("nombreEtapa"),
                        "nombreSubAgrupacion": p.get("nombreSubAgrupacion"),
                        "nombreModelo": p.get("nombreModelo"),
                        "precio": p.get("precio"),
                        "precioBase": p.get("precioBase"),
                        "descuentomaximo_valor": (p.get("descuentoMaximo") or {}).get("valor"),
                        "descuentomaximo_unidad": (p.get("descuentoMaximo") or {}).get("unidad"),
                        "descuentocotizadorweb_valor": (p.get("descuentoCotizadorWeb") or {}).get("valor"),
                        "descuentocotizadorweb_unidad": (p.get("descuentoCotizadorWeb") or {}).get("unidad"),
                        "piso": p.get("piso"),
                        "orientacion": p.get("orientacion"),
                        "fechaCreacion": p.get("fechaCreacion"),
                        "fechaEdicion": p.get("fechaEdicion"),
                        "tipo_producto": p.get("tipo_producto"),
                        "programa": p.get("programa"),
                        "superficies_terraza": (p.get("superficies") or {}).get("terraza"),
                        "superficies_interior": (p.get("superficies") or {}).get("interior"),
                        "superficies_municipal": (p.get("superficies") or {}).get("municipal"),
                        "superficies_util": (p.get("superficies") or {}).get("util"),
                        "superficies_total": (p.get("superficies") or {}).get("total"),
                        "superficies_loggia": (p.get("superficies") or {}).get("loggia"),
                        "superficies_jardin": (p.get("superficies") or {}).get("jardin"),
                        "superficies_miradorTerraza": (p.get("superficies") or {}).get("miradorTerraza"),
                        "superficies_otrasSuperficies": (p.get("superficies") or {}).get("otrasSuperficies"),
                        "superficies_terreno": (p.get("superficies") or {}).get("terreno"),
                        "disponibleWeb": p.get("disponibleWeb"),
                        "disponibleSistema": p.get("disponibleSistema"),
                        "estado": p.get("estado"),
                        "calificacionEnergetica": p.get("calificacionEnergetica"),
                        "comentario": p.get("comentario"),
                        "cantDormitorios": p.get("cantDormitorios"),
                        "cantBanos": p.get("cantBanos"),
                    }
                    cursor.execute(sql, record)
                    insertados += 1

                connection.commit()

        logging.info(f"Productos insertados/actualizados para proyecto {id_proyecto}: {insertados}")
        return insertados

    except Exception as e:
        logging.error(f"Error al obtener productos de proyecto {id_proyecto}: {e}", exc_info=True)
        return 0

def crear_cotizacion(
    id_cliente,
    id_producto_principal,
    productos_secundarios=None,
    productos_adicionales=None,
    id_medio_llegada=0,
    id_tipo_iva=1,
    codigo_cupon=None,
    id_expectativa=0,
    id_razon_compra=0,
    comentario="",
    id_canal_distancia=0,
    utm=None,
    gclid=None
):
    """
    Crea una cotización en la API GCI según el esquema oficial.
    Retorna el id_cotizacion y hashCotizacion si la creación fue exitosa.
    """

    token = get_cached_token()
    url = "https://api-gci-rest.integracionplanok.io/api/cotizaciones?secuencial=false&descuento_producto=false"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
        "accept": "application/json"
    }

    # Fecha actual ISO
    fecha_iso = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")

    # Estructura principal según API
    payload = [{
        "productoPrincipal": {
            "id": int(id_producto_principal),
            "descuento": {"valor": 0, "unidad": "%"}
        },
        "productosSecundarios": [
            {
                "id": int(p.get("id")),
                "descuento": {"valor": p.get("descuento_valor", 0), "unidad": p.get("descuento_unidad", "%")}
            } for p in (productos_secundarios or [])
        ],
        "productosAdicionales": [
            {
                "id": int(p.get("id")),
                "descuento": {"valor": p.get("descuento_valor", 0), "unidad": p.get("descuento_unidad", "%")}
            } for p in (productos_adicionales or [])
        ],
        "idCliente": int(id_cliente),
        "idTipoIVA": int(id_tipo_iva),
        "fecha": fecha_iso,
        "idMedioLlegada": int(id_medio_llegada or 0),
        "telefonoValidado": True,
        "codigo_cupon": codigo_cupon or "",
        "evaluacion": {
            "idExpectativa": int(id_expectativa or 0),
            "idRazonDeCompra": int(id_razon_compra or 0),
            "fechaRecontacto": fecha_iso,
            "comentario": comentario or "",
            "idCanalADistancia": int(id_canal_distancia or 0)
        },
        "utm": utm or {
            "source": "",
            "medium": "",
            "campaign": "",
            "term": "",
            "content": ""
        },
        "gclid": gclid or ""
    }]

    try:
        logging.info(f"Enviando cotización a GCI:\n{json.dumps(payload, ensure_ascii=False, indent=2)}")
        response = requests.post(url, headers=headers, json=payload, timeout=20)
        response.raise_for_status()
        data = response.json()

        if isinstance(data, dict) and data.get("hasError") is False:
            logging.info(f"✅ Cotización creada exitosamente. ID={data.get('id_cotizacion')} HASH={data.get('hashCotizacion')}")
            return data
        elif isinstance(data, dict) and data.get("hasError"):
            logging.warning(f"⚠️ Error al crear cotización: {data.get('error')}")
            return None
        else:
            logging.info(f"Respuesta inesperada al crear cotización: {data}")
            return data

    except requests.exceptions.RequestException as e:
        logging.error(f"Error al crear cotización: {e}", exc_info=True)
        return None


def consultar_cotizacion(id_cotizacion):
    """
    Consulta los datos de una cotización existente en la API GCI.
    Retorna un dict con toda la información de la cotización.
    """
    token = get_cached_token()
    url = f"https://api-gci-rest.integracionplanok.io/api/cotizaciones/{id_cotizacion}"
    headers = {
        "Authorization": f"Bearer {token}",
        "accept": "application/json"
    }

    try:
        response = requests.get(url, headers=headers, timeout=15)
        if response.status_code == 404:
            logging.warning(f"No se encontró la cotización {id_cotizacion}.")
            return None
        response.raise_for_status()
        data = response.json()

        if isinstance(data, dict) and data.get("id") is not None:
            logging.info(f"✅ Cotización {id_cotizacion} recuperada correctamente.")
            return data
        else:
            logging.warning(f"Formato de respuesta inesperado al consultar cotización {id_cotizacion}: {data}")
            return None

    except requests.exceptions.RequestException as e:
        logging.error(f"Error al consultar cotización {id_cotizacion}: {e}", exc_info=True)
        return None
