import os
import json
import time
import logging
from datetime import datetime, timedelta
import pymysql
import requests
from dotenv import load_dotenv
import re
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"

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():
    """
    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 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.")
    else:
        logging.info("No existe archivo de caché de token GCI, se solicitará un token nuevo.")

    # 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 _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 campo obligatorio
        "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
