# -*- coding: utf-8 -*-

import re
import unidecode
import os
import time
import inspect

from datetime import datetime

from utiles import respuesta, borrar_emojis, timeit

from bd.bdmemoria import obtener_id_ultimo_mensaje

from estaticos.ids import clases as ids

from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


def es_una_cita(mensaje):
    return mensaje.find_elements_by_css_selector(ids['tipos_mensaje']['cita'])


def es_un_mapa(mensaje):

    mapa = mensaje.find_elements_by_xpath(ids['tipos_mensaje']['mapa'])


    # import web_pdb
    # web_pdb.set_trace()
    # mapa = mensaje.find_elements_by_css_selector(ids['tipos_mensaje']['mapa'])
    if len(mapa) == 0:
        return []
    # si es un mapa
    else:
        # Si encontramos una coincidencia en el selector img[crossorigin='anonymous']
        # Intentamos obtener el src del elemento, y verificamos que no sea un emoji
        # Ya que se identifican con el mismo selector que las ubicaciones
        src_mapa = mapa[0].get_attribute("href")

        # # si era un emoji
        if "maps" not in src_mapa:
            return []  # Devolvemos la lista vacia
        # si no era un emoji
        else:
            return src_mapa


def es_un_audio(mensaje):
    return mensaje.find_elements_by_css_selector(ids['tipos_mensaje']['audio'])


def es_un_texto(mensaje):
    return mensaje.find_elements_by_css_selector(ids['tipos_mensaje']['texto'])


def formatear_fecha_hora(fecha_hora):
    """
    Esta funcion se encarga de agregar un cero donde corresponda en el mes y el dia

    :param fecha_hora: (str) Un string que contiene la fecha y la hora del mensaje
    :return: (str) La fecha y hora formateada
    """
    hora = fecha_hora.split(' ')[0]  # Separamos y guardamos la hora solo
    fecha = fecha_hora.split(' ')[1]  # Separamos y guardamos la fecha solo

    dia = fecha.split('/')[0]  # Separamos la fecha y guardamos el dia
    mes = fecha.split('/')[1]  # Separamos la fecha y guardamos el mes
    ano = fecha.split('/')[2]  # Separamos la fecha y guardamos el año

    if len(dia) == 1:  # Si al dia le falta el cero
        dia = "0" + dia  # Le agregamos el cero detras

    if len(mes) == 1:  # Si al mes le falta un cero
        mes = "0" + mes  # Le agregamos el cero detras

    # formateada = hora + " " + dia + "/" + mes + "/" + ano  # Armamos la fecha
    formateada = ano + "/" + mes + "/" + dia + " " + hora # Armamos la fecha final con nuevo formato
    return formateada  # Devolvemos la fecha formateada


@timeit
def obtener_hora_mensaje(mensaje, nombre_agendado):
    # Intentamos obtener el elemento que contiene la fecha y hora
    elemento_con_hora = mensaje.find_elements_by_css_selector(ids['chat']['elemento_hora'])

    if len(elemento_con_hora) == 0:  # Si no pudimos obtener el elemento con hora
        return False  # Devolvemos False para futura evaluacion

    # Obtenemos el atributo que contiene la hora y fecha, borramos los corchetes izquierdos
    sin_inicial = elemento_con_hora[0].get_attribute(ids['chat']['hora_mensaje']).replace('[', '')

    sin_nada = sin_inicial.replace(']', '')  # Borramos los corchetes derechos

    fecha = sin_nada.replace(',', '')  # Borramos las comas

    fecha = fecha.replace(nombre_agendado, '').rstrip()[0:-2]  # Borramos el numero de la persona y los espacios

    cortado = fecha.split()
    if len(cortado) > 2:
        hora_antes = cortado[0]  # entrada 15:00. sale 15
        am_pm = cortado[1][0]  # entra 'a. m.' sale 'a'
        hora_despues = pasar_hora_am_pm_a_numero(hora_antes, am_pm)
        cortado[0] = hora_despues  # obtiene la hora
        del cortado[1]  # borra la a
        del cortado[1]  # borra la m
        fecha = " ".join(cortado)  # pasa de array string

    # reemplaza p. m. por p.m. y a. m. por a.m.
    return fecha  # Devolvemos la hora formateada


def pasar_hora_am_pm_a_numero(hora, mediodia):
    # print("hora:", hora)
    # print("mediodia:", mediodia)
    hora_dividida = hora.split(':')
    # print("hora despues:", hora_dividida)
    hora = hora_dividida[0]
    hora_formateada = None
    # print("hora ntes if:", hora)
    if mediodia == 'a':
        if hora == '1' or hora == '01' or hora == '13':
            hora_formateada = '01'
        elif hora == '2' or hora == '02' or hora == '14':
            hora_formateada = '02'
        elif hora == '3' or hora == '03' or hora == '15':
            hora_formateada = '03'
        elif hora == '4' or hora == '04' or hora == '16':
            hora_formateada = '04'
        elif hora == '5' or hora == '05' or hora == '17':
            hora_formateada = '05'
        elif hora == '6' or hora == '06' or hora == '18':
            hora_formateada = '06'
        elif hora == '7' or hora == '07' or hora == '19':
            hora_formateada = '07'
        elif hora == '8' or hora == '08' or hora == '20':
            hora_formateada = '08'
        elif hora == '9' or hora == '09' or hora == '21':
            hora_formateada = '09'
        elif hora == '10' or hora == '22':
            hora_formateada = '10'
        elif hora == '11' or hora == '23':
            hora_formateada = '11'
        elif hora == '12' or hora == '00':
            hora_formateada = '12'
    elif mediodia == 'p':
        if hora == '1' or hora == '01' or hora == '13':
            hora_formateada = '13'
        elif hora == '2' or hora == '02' or hora == '14':
            hora_formateada = '14'
        elif hora == '3' or hora == '03' or hora == '15':
            hora_formateada = '15'
        elif hora == '4' or hora == '04' or hora == '16':
            hora_formateada = '16'
        elif hora == '5' or hora == '05' or hora == '17':
            hora_formateada = '17'
        elif hora == '6' or hora == '06' or hora == '18':
            hora_formateada = '18'
        elif hora == '7' or hora == '07' or hora == '19':
            hora_formateada = '19'
        elif hora == '8' or hora == '08' or hora == '20':
            hora_formateada = '20'
        elif hora == '9' or hora == '09' or hora == '21':
            hora_formateada = '21'
        elif hora == '10' or hora == '22':
            hora_formateada = '22'
        elif hora == '11' or hora == '23':
            hora_formateada = '23'
        elif hora == '12' or hora == '00':
            hora_formateada = '12'
    # print("hora_forM:", hora_formateada)
    # print("hora1:", hora_dividida[1])
    salida = hora_formateada + ':' + hora_dividida[1]
    # print("slaidaasda:", salida)
    return salida


def formatear_link_maps(link_maps):
    """
    Esta funcion se encarga de formatear un link de google maps para obtener solamente latitud y longitud

    :param link_maps: (str) Un string que contiene el link de google maps a una ubicacion especifica
    :return: (str) Un string conteniendo latitud y longitud separados por coma (ej. "-37.32452,-60.436547")
    """

    regex = "(?<=center=)(.*)(?=&)"  # Guardamos el regex que utilizaremos para obtener las coordenadas
    regex_alternativo = "(?<=7C)(.*)(?=&signature)"  # Guardamos el regex por si falla el primero

    coincidencias = re.findall(regex, link_maps)  # Obtenemos las coordenadas sin formatear

    if len(coincidencias) == 0:  # Si no encontramos las coordenadas

        # Intentamos con el regex alternativo
        coincidencias = re.findall(regex_alternativo, link_maps)

        if len(coincidencias) == 0:  # Si tampoco encontramos coincidencias con el regex alternativo
            return False  # Devolvemos False para futura validacion

    coordenadas = coincidencias[0].replace('%2C+', ',')  # Formateamos las coordenadas

    return coordenadas  # Devolvemos las coordenadas


# @timeit
def obtener_lat_lon_desde_ruta(ruta):
    """
    formatea el link del mapa para obtener la latitud y longitud

    :param ruta: 'https://maps.google.com/maps?q=-32.9333949%2C-60.7183358&z=17&hl=es'
    :return:'-32.9333949,-60.7183358'
    """
    if not ruta:
        return None

    # Expresion regular que coincide con la latitud y longitud
    mapa_re = r"-\d{1,2}\.\d{0,}"  # -\d{1,2}(\.)\d{0,} sin el parentesis
    encontrar = re.findall(mapa_re, ruta)  # busca todas las coincidencias y las retorna en una lista
    print("encontrar", encontrar)
    texto = encontrar[0] + ',' + encontrar[1]    
    print(texto)

    # texto = ruta.replace('https://maps.google.com/maps?q=', '')
    # texto = texto.replace('%2C', ',')
    # texto = texto.replace('&z=17&hl=es', '')

    return texto


def obtener_texto_mensaje(mensaje, tipo_mensaje):
    """
    Esta funcion se encarga de obtener el texto que se debe enviar al servidor.

    :param mensaje: (Selenium) Un objeto de selenium que contiene datos del mensaje
    :param tipo_mensaje: (str) Un string que define el tipo de mensaje
    :return: (dict) Un diccionario con estado, una respuesta y de ser posible un dato
    """

    if tipo_mensaje == 'audio':  # Si el mensaje es un audio
        return respuesta(True, u'No se aceptan audios',
                         dato="USUARIO ENVIO AUDIO")  # Devolvemos una respuesta con False

    if tipo_mensaje == 'cita':  # Si el mensaje es una cita
        texto = mensaje.text  # Guardamos el texto citado
        return respuesta(True, u'Se obtuvo el texto de la cita con exito', dato=texto)

    if tipo_mensaje == 'mapa':  # Si el mensaje es un mapa
        # link_maps = mensaje.get_attribute('src')  # Obtenemos el link de google maps
        #
        # texto = formatear_link_maps(link_maps)  # Formateamos el link de google maps
        #
        # if texto is False:  # Si no pudimos formatear el link
        #     return respuesta(False, u'No se pudo formatear el link de google maps')
        texto = obtener_lat_lon_desde_ruta(mensaje)  # Formateamos el link de google maps

        if not texto:  # Si no pudimos formatear el link
            return respuesta(False, u'No se pudo formatear el link de google maps')

        return respuesta(True, u'Se formateo con exito el link de google maps', dato=texto)

    if tipo_mensaje == 'texto':  # Si el mensaje es un texto
        try:
            texto = mensaje.find_element_by_css_selector("#text")
        except:
            texto = mensaje.text  # Obtenemos el texto del mensaje

        return respuesta(True, u'Se obtuvo el texto del mensaje con exito', dato=texto)


def evaluar_mensaje(mensaje):
    # Llamamos a cada funcion correspondiente para analizar el mensaje

    texto = es_un_texto(mensaje)
    if len(texto) > 0:  # Si el mensaje es un texto
        dic = {
            'tipo_mensaje': 'texto',
            'mensaje': texto[0],
            'clase': 'txt'
        }

        # Devolvemos el diccionario con el mensaje y su tipo
        return respuesta(True, u'Se encontro un texto', dato=dic)

    mapa = es_un_mapa(mensaje)
    if len(mapa) > 0:  # Si el mensaje es un mapa
        dic = {
            'tipo_mensaje': 'mapa',
            'mensaje': mapa,
            'clase': 'mapa'
        }

        # Devolvemos el diccionario con el mensaje y su tipo
        return respuesta(True, u'Se encontro una ubicacion', dato=dic)

    cita = es_una_cita(mensaje)
    if len(cita) > 0:  # Si el mensaje es una cita
        dic = {
            'tipo_mensaje': 'cita',
            'mensaje': cita[0],
            'clase': 'txt'
        }

        # Devolvemos el diccionario con el mensaje y su tipo
        return respuesta(True, u'Se encontro una cita', dato=dic)

    audio = es_un_audio(mensaje)
    if len(audio) > 0:  # Si el mensaje es un audio
        dic = {
            'tipo_mensaje': 'audio',
            'mensaje': audio[0],
            'clase': 'audio'
        }

        # Devolvemos el diccionario con el mensaje y su tipo
        return respuesta(True, u'Se encontro un audio', dato=dic)

    # Si no era de ningun tipo devolvemos respuesta con False y un mensaje
    return respuesta(False, u'No se encontro un tipo para el mensaje')


def buscar_elemento_selector_xpath(selector, driver,
                                   timeout=10, clickeable=False, visible=False, presente=False):
    """
    Esta funcion se encarga de buscar un elemento mediante su selector XPATH.
    Dependiendo los parametros ingresados, esperaremos que el elemento sea clickeable, visible o presente.
    """

    espera = WebDriverWait(driver, timeout)  # Inicializamos el objeto de espera de Selenium

    if clickeable:  # Si se necesita que el elemento sea clickeable
        return espera.until(EC.element_to_be_clickable((By.XPATH, selector)))

    elif visible:  # Si se necesita que el elemento este visible en pantalla
        return espera.until(EC.visibility_of_element_located((By.XPATH, selector)))

    elif presente:  # Si se necesita que el elemento este presente en el DOM
        return espera.until(EC.presence_of_element_located((By.XPATH, selector)))

    else:  # Si no se especifico ningun evento
        return driver.find_element_by_xpath(selector)  # Buscamos normalmente


def buscar_elemento_selector_css(selector, driver,
                                 timeout=10, clickeable=False, visible=False, presente=False):
    """
    Esta funcion se encarga de buscar un elemento mediante su selector css.
    Dependiendo los parametros ingresados, esperaremos que el elemento sea clickeable, visible o presente
    """

    espera = WebDriverWait(driver, timeout)  # Inicializamos el objeto de espera de Selenium

    if clickeable:  # Si se necesita que el elemento sea clickeable
        return espera.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector)))

    elif visible:  # Si se necesita que el elemento este visible en pantalla
        return espera.until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))

    elif presente:  # Si se necesita que el elemento este presente en el DOM
        return espera.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))

    else:  # Si no se especifico ningun evento
        return driver.find_element_by_css_selector(selector)  # Buscamos normalmente


def verificar_chats(driver):
    """
    Esta funcion se encarga de abrir una sesion de WhatsApp Web y iterar a traves de todos los chats en el
    panel izquierdo de la pantalla, guardando cantidad de mensajes pendientes en cada chat y el nombre/numero.
    """

    # Buscamos todos los chats del panel izquierdo
    chats = driver.find_elements_by_xpath(ids['panel_izquierdo']['chats'])

    if len(chats) == 0:  # Si no se encontro ningun elemento
        return respuesta(False, u'No se encontraron chats')

    lista = list()  # Inicializo la lista que voy a devolver

    # Por cada chat encontrado
    for chat in chats:

        nombre_agendado = chat.find_elements_by_css_selector(ids['panel_izquierdo']['nombre'])

        nombre_agendado = nombre_agendado[0].text

        cantidad_mensajes = obtener_elemento_iconito_verde(chat)

        print("no esta encontrando mensajes en el telefono:", nombre_agendado)
        if len(cantidad_mensajes) == 0:  # Si no se encontraron mensajes pendientes

            # Si tambien se necesitan los chats que no tienen mensajes nuevos
            cantidad_mensajes = 0  # Seteamos la cantidad de mensajes pendientes como 0

        else:  # Si se encontraron mensajes pendientes
            # Seteamos la cantidad de mensajes pendientes
            cantidad_mensajes = cantidad_mensajes[0].get_attribute("aria-label")[0]

        dic = {  # Creamos el diccionario a insertar en la lista
            'nombre_agendado': nombre_agendado,
            "cantidad_mensajes": int(cantidad_mensajes),
        }

        lista.append(dic)  # Agregamos el diccionario a la lista

    if len(lista) == 0:  # Si no se encontraron chats
        return respuesta(False, u'No se encontraron chats')

    driver.quit()

    return respuesta(True, u'Se itero a traves de los chats con exito', dato=lista)


def verificar_mensajes(driver):
    """
    Esta funcion se encarga de abrir cada chat que tenga mensajes nuevos y guardar todos los mensajes asociados
    al chat.
    """
    # Buscamos todos los chats del panel izquierdo
    chats = driver.find_elements_by_xpath(ids['panel_izquierdo']['chats'])

    if len(chats) == 0:  # Si no se encontro ningun elemento
        return respuesta(False, u'No se encontraron chats')

    lista = []

    errores = []

    for chat in chats:

        cantidad_mensajes = obtener_elemento_iconito_verde(chat)

        if len(cantidad_mensajes) == 0:
            continue

        cantidad_mensajes = int(cantidad_mensajes[0].get_attribute("aria-label")[0])

        time.sleep(1)

        # Hacemos click en el div para dirigirnos hacia la ventana del chat
        chat.click()

        nombre_agendado = driver.find_elements_by_xpath(ids['chat']['nombre_agendado'])

        nombre_agendado = nombre_agendado[0].text

        # Buscamos solo los mensajes pendientes del usuario, y damos vuelta la lista
        mensajes = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])

        if len(mensajes) == 0:
            errores.append("No se pudieron encontrar mensajes para %s" % nombre_agendado)
            continue  # Continuamos con el loop

        mensajes = mensajes[-cantidad_mensajes:]  # Obtenemos solo los mensajes pendientes

        for mensaje in mensajes:  # Iteramos a traves de los mensajes

            # Obtenemos el id del mensaje
            id_mensaje = mensaje.get_attribute('data-id')

            telefono = obtener_telefono_desde_id(id_mensaje)

            # Evaluamos el mensaje, y guardamos el resultado en una variable
            evaluacion_mensaje = evaluar_mensaje(mensaje)

            if evaluacion_mensaje['Estado'] is False:  # Si el estado es False
                errores.append("Error al evaluar el mensaje de %s" % nombre_agendado)
                continue

            tipo_mensaje = evaluacion_mensaje['Dato']['tipo_mensaje']  # Guardamos el tipo del mensaje

            elemento_mensaje = evaluacion_mensaje['Dato']['mensaje']  # Guardamos el elemento del mensaje

            clase_mensaje = evaluacion_mensaje['Dato']['clase']  # Guardamos la clase del mensaje

            # Intentamos obtener el texto del mensaje recibido
            rta_obtener_texto = obtener_texto_mensaje(elemento_mensaje, tipo_mensaje)

            if rta_obtener_texto['Estado'] is False:  # Si fallamos al obtener el texto
                errores.append(rta_obtener_texto['Respuesta'])  # Agregamos el error a la lista
                continue  # Continuamos con el loop

            # Guardamos el texto del mensaje en un diccionario y reemplazamos cualquier caracter que no sea ASCII
            texto = unidecode.unidecode(rta_obtener_texto['Dato'])

            # Si el texto es un string vacio
            if texto == '':
                continue  # Continuamos con el loop para no mandarlo al servidor

            # Intentamos obtener la fecha y hora del mensaje
            fecha_hora = obtener_hora_mensaje(mensaje, nombre_agendado)
            print("fecha_hora 382:",fecha_hora)
            if fecha_hora is False:
                continue

            fecha_hora_formateada = formatear_fecha_hora(fecha_hora)

            lista.append(
                (id_mensaje, telefono, nombre_agendado, texto, clase_mensaje, fecha_hora_formateada, 'pendiente'))

    if len(errores) > 0:
        return respuesta(estado=False,
                         respuesta="Hubo errores al monitorear los mensajes")

    return respuesta(True, u'Se obtuvieron los mensajes', dato=lista)


# @timeit
def obtener_telefono_desde_id(id_mensaje):
    """
    Esta funcion se encarga de cortar el id del mensaje para obtener tan solo el telefono de la persona
    que envio ese mensaje.
    """

    regex = "_(.*)@"

    numero = "+" + re.findall(regex, id_mensaje)[0]

    return numero


def formatear_mensaje(mensaje):
    """
    Esta funcion se encarga de formatear un mensaje, en cada \n (salto de linea)  separara el string y
    devolvera una lista.
    """
    return mensaje.split("\\n")


def formatear_telefono(telefono):
    """
    Esta funcion se encarga de formatear un telefono dado.
    Por ejemplo: "+5493415846599" -> "+54 9 3415 84-6599"
    """

    if len(telefono) > 14 or len(telefono) < 14:
        return telefono

    return "{} {} {} {}-{}".format(telefono[:3], telefono[3], telefono[4:8], telefono[8:10], telefono[10:])


def enviar_mensaje_sin_buscador(driver, mensaje):
    """
    Esta funcion se encarga de enviar un mensaje sin utilizar el buscador, es decir, escribiendo directamente
    en el input de la ventana del chat y enviando.
    """

    # print("\nva a querer enviar el mensaje:",mensaje)
    # Buscamos el input para enviar mensajes
    inp_enviar = buscar_elemento_selector_xpath(ids['chat']['input_enviar_mensaje'],
                                                driver,
                                                presente=True)
    # print("inp_enviar:",inp_enviar)
    inp_enviar.click()

    # Formateamos el mensaje
    mensaje_formateado = formatear_mensaje(mensaje)

    # Escribimos el mensaje respetando el formateo dado
    for linea in mensaje_formateado:
        inp_enviar.send_keys(linea)  # Escribimos la linea

        inp_enviar.send_keys(Keys.LEFT_ALT, Keys.ENTER)  # Efectuamos un salto de linea EN WHATSAPP

    inp_enviar.send_keys(Keys.ENTER)  # Enviamos el mensaje formateado

    return respuesta(True, u'Se envio el mensaje correctamente')


def enviar_mensaje_con_buscador(driver, mensaje, telefono, nombre_agendado):
    """
    Esta funcion se encarga de enviar un mensaje utilizando el buscador, idoneamente debe ejecutarse solo si el
    chat abierto en el momento no corresponde con la persona a la que debemos enviar mensaje.
    """

    # Intentamos conseguir el elemento para buscar personas
    buscador = buscar_elemento_selector_xpath(ids['panel_izquierdo']['buscador'],
                                              driver,
                                              timeout=30,
                                              clickeable=True)

    buscador.send_keys(telefono)  # Escribimos el telefono de la persona

    nombre_formateado = "'" + nombre_agendado + "'"  # Agregamos comillas simples al nombre agendado

    # Buscamos los elementos que corresponden con el del nombre agendado
    chats = driver.find_elements_by_xpath(f"//span[contains(@title, {nombre_formateado})]")

    for chat in chats:

        chat.click()

        # Obtenemos uno de los mensajes enviados por el usuario
        mensaje_random = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])

        if len(mensaje_random) == 0:  # Si no se encontraron mensajes en la ventana
            continue  # Continuamos con el loop

        # Obtenemos la id de del mensaje
        id_mensaje = mensaje_random[0].get_attribute("data-id")

        # Desde la id del mensaje obtenemos el numero de telefono, WhatsApp guarda en todas las ids de los
        # mensajes recibidos el numero de telefono REAL de la persona
        telefono_en_chat = obtener_telefono_desde_id(id_mensaje)

        if telefono_en_chat != telefono:  # Si no coinciden los telefonos seguimos buscando
            continue

        # Buscamos el input para enviar mensajes
        inp_enviar = buscar_elemento_selector_xpath(ids['chat']['input_enviar_mensaje'],
                                                    driver,
                                                    presente=True)

        inp_enviar.click()

        # Formateamos el mensaje
        mensaje_formateado = formatear_mensaje(mensaje)

        # Escribimos el mensaje respetando el formateo dado
        for linea in mensaje_formateado:
            inp_enviar.send_keys(linea)  # Escribimos la linea

            inp_enviar.send_keys(Keys.LEFT_ALT, Keys.ENTER)  # Efectuamos un salto de linea EN WHATSAPP

        inp_enviar.send_keys(Keys.ENTER)  # Enviamos el mensaje formateado

    return respuesta(True, u'Se envio el mensaje correctamente')


def enviar_mensajes_desde_chats(driver, mensaje, telefono, nombre_agendado):
    """Funcion encargada de enviar un mensaje buscando al usuario correspondiente en la lista de chats."""
    chats = driver.find_elements_by_xpath(ids['panel_izquierdo']['chats'])

    if len(chats) == 0:
        return respuesta(estado=False,
                         respuesta="No se encontraron chats para buscar el mensaje")

    for chat in chats:
        # Buscamos el nombre al que debemos enviar el mensaje en los chats
        nombre = chat.find_elements_by_css_selector(ids['panel_izquierdo']['nombre'])

        if len(nombre) == 0:
            continue

        nombre = nombre[0]

        if nombre != nombre_agendado:
            continue

        nombre.click()  # Hacemos click en el nombre para abrir el chat

        # Buscamos el input para enviar mensajes
        input_mensaje = driver.find_elements_by_xpath(ids['chat']['input_enviar_mensaje'])

        if len(input_mensaje) == 0:
            return respuesta(estado=False,
                             respuesta="No se encuentra el input para enviar mensajes")

        input_mensaje = input_mensaje[0]

        input_mensaje.click()

        # Formateamos el mensaje
        mensaje_formateado = formatear_mensaje(mensaje)

        # Escribimos el mensaje respetando el formateo dado
        for linea in mensaje_formateado:
            input_mensaje.send_keys(linea)  # Escribimos la linea

            input_mensaje.send_keys(Keys.LEFT_ALT, Keys.ENTER)  # Efectuamos un salto de linea EN WHATSAPP

        input_mensaje.send_keys(Keys.ENTER)  # Enviamos el mensaje formateado

    return respuesta(estado=True,
                     respuesta="Se envio el mensaje utilizando los chats de la izquierda")


def enviar_mensaje(driver, mensaje, telefono, nombre_agendado):
    """Esta funcion se encarga de enviar un mensaje a un telefono dado"""
    # print("trata de enviar un mensaje")
    nombre_chat = driver.find_elements_by_xpath(ids['chat']['nombre_agendado'])

    if len(nombre_chat) > 0:  # Si hay un chat abierto

        nombre_chat = nombre_chat[0].text

        if nombre_chat == nombre_agendado:  # Si es el chat de la persona que debemos enviar
            return enviar_mensaje_sin_buscador(driver, mensaje)

    chats = driver.find_elements_by_xpath(ids['panel_izquierdo']['chats'])

    for chat in chats:

        persona = chat.find_elements_by_css_selector(ids['panel_izquierdo']['nombre'])

        if len(persona) == 0:
            continue

        nombre_persona = persona[0].text

        nombre_como_telefono = nombre_persona.replace(' ', '').replace('-', '')

        # Borramos los espacios y el guion medio del nombre agendado y compramos
        if nombre_persona == nombre_agendado or nombre_como_telefono == nombre_agendado:
            chat.click()

            rta = enviar_mensaje_sin_buscador(driver, mensaje)

            return rta

    return respuesta(estado=False,
                     respuesta="No se encontro al usuario para enviarle el mensaje")


async def verificar_estado_de_la_sesion(driver):
    """
    Esta funcion se encarga de verificar que la pagina de WhatsApp haya cargado correctamente y ya podemos
    seleccionar chats, escribir mensajes, etc.
    """

    # TODO: Agregar medicion por tiempo en lugar de vueltas.

    no_cargo_pagina = True
    vueltas = int()
    while no_cargo_pagina:

        if vueltas > 10000:
            return respuesta(False, u'Problema al cargar la pagina de WhatsApp, revise el perfil o el internet')

        elem = driver.find_elements_by_css_selector(ids['verificar_sesion'])

        if elem:
            no_cargo_pagina = False
        else:
            vueltas += 1

    return respuesta(True, u'La sesion esta iniciada')


def formatear_nombre_agendado(hora_nombre):
    """
    Esta funcion se encarga de obtener el nombre agendado de una persona a partir de un string obtenido de un
    elemento que corresponde a los mensajes recibidos de WhatsApp.
    El formato del string inicial es como el siguiente: [12:01, 8/3/2021] +54 9 3415 84-6599:
    :param nombre_agendado:
    :return:
    """

    # El regex que utilizaremos encuentra todos los caracteres dentro de ']' y ':'
    regex = "\]([^;]*):"

    nombre_agendado = re.findall(regex, hora_nombre)[0].lstrip().rstrip()

    return nombre_agendado


def buscar_elemento_en_loop_css(driver, selector, intentos=10):
    cant_intentos = int()

    while cant_intentos < intentos:

        try:
            elemento = driver.find_elements_by_css_selector(selector)
            return elemento
        except:
            continue
        finally:
            cant_intentos += 1


@timeit
def formatear_mensajes(driver, mensajes, como_diccionario=False):
    lista = []

    errores = []

    for mensaje in mensajes:  # Iteramos a traves de los mensajes
        # Evaluamos el mensaje, y guardamos el resultado en una variable
        evaluacion_mensaje = evaluar_mensaje(mensaje)  # 15ms
        id_mensaje = obtener_id_mensaje(mensaje)  # 6
        telefono = obtener_telefono_desde_id(id_mensaje)

        if not evaluacion_mensaje['Estado']:  # Si el estado es False
            errores.append("Error al evaluar el mensaje de %s" % telefono)
            continue

        tipo_mensaje = evaluacion_mensaje['Dato']['tipo_mensaje']  # Guardamos el tipo del mensaje

        elemento_mensaje = evaluacion_mensaje['Dato']['mensaje']  # Guardamos el elemento del mensaje

        clase_mensaje = evaluacion_mensaje['Dato']['clase']  # Guardamos la clase del mensaje
        # Obtenemos el id del mensaje
        print("entra 762")
        # Intentamos obtener el texto del mensaje recibido
        rta_obtener_texto = obtener_texto_mensaje(elemento_mensaje, tipo_mensaje)  # 8ms

        if not rta_obtener_texto['Estado']:  # Si fallamos al obtener el texto
            errores.append(rta_obtener_texto['Respuesta'])  # Agregamos el error a la lista
            continue  # Continuamos con el loop

        # Guardamos el texto del mensaje en un diccionario y reemplazamos cualquier caracter que no sea ASCII
        texto = unidecode.unidecode(rta_obtener_texto['Dato'])

        # Si el texto es un string vacio
        if texto == '':
            continue  # Continuamos con el loop para no mandarlo al servidor

        nombre_agendado = obtener_nombre_agendado(driver)  # 10
        # Intentamos obtener la fecha y hora del mensaje
        fecha_hora = obtener_hora_mensaje(mensaje, nombre_agendado)
        # print("fecha_hora :", fecha_hora)
        if not fecha_hora:
            continue

        fecha_hora_formateada = formatear_fecha_hora(fecha_hora)

        # Si precisamos un diccionario para futura evaluacion
        if como_diccionario:
            dic = {
                'id_mensaje': id_mensaje,
                'telefono': telefono,
                'nombre_agendado': nombre_agendado,
                'texto': texto,
                'clase': clase_mensaje,
                'fecha_hora': fecha_hora_formateada
            }

            lista.append(dic)

            continue

        # Por defecto devolvemos una tupla con los elementos para su insercion en la base de datos
        lista.append((id_mensaje, telefono, nombre_agendado, texto, clase_mensaje, fecha_hora_formateada, 'pendiente'))

    if not lista:
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)

    return respuesta(True, u'Se formatearon los mensajes con exito', dato=lista)


@timeit
def formatear_mensaje_uno_solo(driver, mensaje, como_diccionario=False):
    lista = []

    errores = []

    # Evaluamos el mensaje, y guardamos el resultado en una variable
    evaluacion_mensaje = evaluar_mensaje(mensaje)  # 15ms
    id_mensaje = obtener_id_mensaje(mensaje)  # 6
    telefono = obtener_telefono_desde_id(id_mensaje)

    if not evaluacion_mensaje['Estado']:  # Si el estado es False
        errores.append("Error al evaluar el mensaje de %s" % telefono)
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)

    tipo_mensaje = evaluacion_mensaje['Dato']['tipo_mensaje']  # Guardamos el tipo del mensaje

    elemento_mensaje = evaluacion_mensaje['Dato']['mensaje']  # Guardamos el elemento del mensaje

    clase_mensaje = evaluacion_mensaje['Dato']['clase']  # Guardamos la clase del mensaje
    # Obtenemos el id del mensaje

    # Intentamos obtener el texto del mensaje recibido
    rta_obtener_texto = obtener_texto_mensaje(elemento_mensaje, tipo_mensaje)  # 8ms

    if not rta_obtener_texto['Estado']:  # Si fallamos al obtener el texto
        errores.append(rta_obtener_texto['Respuesta'])  # Agregamos el error a la lista
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)

        # Guardamos el texto del mensaje en un diccionario y reemplazamos cualquier caracter que no sea ASCII
    texto = unidecode.unidecode(rta_obtener_texto['Dato'])

    # Si el texto es un string vacio
    if texto == '':
        errores.append('vacio')  # Agregamos el error a la lista
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)

    nombre_agendado = obtener_nombre_agendado(driver)  # 10
    # Intentamos obtener la fecha y hora del mensaje
    fecha_hora = obtener_hora_mensaje(mensaje, nombre_agendado)
    # print("fecha_hora :", fecha_hora)
    if not fecha_hora:
        errores.append('fecha_hora_vacia')  # Agregamos el error a la lista
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)
    fecha_hora_formateada = formatear_fecha_hora(fecha_hora)

    # Si precisamos un diccionario para futura evaluacion
    if como_diccionario:
        dic = {
            'id_mensaje': id_mensaje,
            'telefono': telefono,
            'nombre_agendado': nombre_agendado,
            'texto': texto,
            'clase': clase_mensaje,
            'fecha_hora': fecha_hora_formateada
        }

        lista.append(dic)

    if not lista:
        return respuesta(estado=False,
                         respuesta="No se pudieron formatear los mensajes, errores: %s" % errores)

    return respuesta(True, u'Se formatearon los mensajes con exito', dato=lista)

def obtener_elemento_iconito_verde(chat):
    salida = chat.find_elements_by_css_selector(ids['panel_izquierdo']['cantidad_mensajes'])
    # salida=chat.find_elements_by_xpath(ids['panel_izquierdo']['cantidad_mensajes'])
    # import web_pdb
    # web_pdb.set_trace()
    return salida

def obtener_nombre_agendado(driver):
    return driver.find_elements_by_xpath(ids['chat']['nombre_agendado'])[0].text


def obtener_id_mensaje(mensaje):
    return mensaje.get_attribute('data-id')


def obtener_ultimo_mensaje(driver):
    """
    Esta funcion se encarga de obtener la id del ultimo mensaje de un usuario dado.
    """

    # Obtenemos todos los mensajes enviados por el usuario
    mensajes = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])

    if len(mensajes) == 0:
        return respuesta(False, u'No se encontraron mensajes entrantes del usuario')

    mensajes.reverse()

    id_ultimo_mensaje = mensajes[0].get_attribute('data-id')

    return respuesta(True, u'Se obtuvo el id del ultimo mensaje con exito', dato=id_ultimo_mensaje)


def no_hay_chat_abierto(driver):
    # Verificamos si hay un chat abierto en el momento de llamar esta funcion
    sin_chat = bool(driver.find_elements_by_css_selector(ids['chat']['chat_abierto']))

    return sin_chat


async def monitorear_chat_abierto(driver, ultimo_mensaje_bd):
    """
    Esta funcion se encarga de monitorear un chat abierto, verificando si entran mensajes nuevos.
    """

    rta_ultimo_mensaje = obtener_ultimo_mensaje(driver)

    if rta_ultimo_mensaje['Estado'] is False:
        return rta_ultimo_mensaje

    ultimo_mensaje_nav = rta_ultimo_mensaje['Dato']

    if ultimo_mensaje_nav == ultimo_mensaje_bd:
        return respuesta(estado=True,
                         respuesta=u'No hay mensajes nuevos')

    # Obtenemos todos los mensajes enviados por el usuario
    mensajes = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])

    if len(mensajes) == 0:
        return respuesta(False, u'No se encontraron mensajes entrantes del usuario')

    mensajes.reverse()

    rta_mensajes = formatear_mensajes(driver, mensajes)

    return rta_mensajes


def obtener_contactos(driver):
    """
    Esta funcion se encarga de iterar a traves de los chats que estan activos o estuvieron activos en algun momento,
    hacer click en cada uno de ellos y obtener tanto el numero real de la persona como el nombre con el que se
    lo tiene agendado
    """

    # Buscamos todos los chats del panel izquierdo
    chats = driver.find_elements_by_xpath(ids['panel_izquierdo']['chats'])

    if len(chats) == 0:  # Si no se encontro ningun elemento
        return respuesta(False, u'No se encontraron chats')

    chats.reverse()

    lista = list()  # Inicializo la lista que devolveremos
    mensajes_pendientes = list()

    for chat in chats:  # Iteramos a traves de los chats

        nombre_agendado = chat.find_elements_by_css_selector(ids['panel_izquierdo']['nombre'])

        hay_mensajes = chat.find_elements_by_css_selector(ids['panel_izquierdo']['cantidad_mensajes'])

        cantidad_mensajes = int(hay_mensajes[0].text) if hay_mensajes else 0

        if len(nombre_agendado) == 0:  # Si no encontramos el nombre de la persona
            return respuesta(False, u'No se encontro el nombre de la persona en panel izquierdo')

        nombre_agendado = nombre_agendado[0].text

        chat.click()  # Hacemos click en el chat para abrirlo

        if hay_mensajes:  # Si el chat tiene mensajes
            mensajes = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])[-cantidad_mensajes:]

            rta_mensajes = formatear_mensajes(driver, mensajes)

            if 'Dato' in rta_mensajes.keys():
                mensajes_pendientes.append(rta_mensajes['Dato'])

        # Obtenemos uno de los mensajes enviados por el usuario
        mensaje_random = driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])

        if len(mensaje_random) == 0:  # Si no se encontraron mensajes en la ventana
            continue  # Continuamos con el loop

        # Obtenemos la id de del mensaje
        id_mensaje = mensaje_random[0].get_attribute("data-id")

        # Desde la id del mensaje obtenemos el numero de telefono, WhatsApp guarda en todas las ids de los
        # mensajes recibidos el numero de telefono REAL de la persona
        telefono_en_chat = obtener_telefono_desde_id(id_mensaje)

        lista.append({'telefono': telefono_en_chat, 'nombre_agendado': nombre_agendado})

    return respuesta(True, u'Se obtuvieron todos lo contactos con exito',
                     dato={'usuarios': lista, 'mensajes': mensajes_pendientes})


@timeit
def monitorear_ventana(driver):
    # Buscamos dentro del chat el ultimo mensaje
    mensajes = extraer_mensajes(driver, 4)  # 4ms

    if not mensajes:
        return respuesta(False, 'No se encontraron mensajes entrantes en el chat')


    formateados = formatear_mensajes(driver, mensajes, como_diccionario=True)

    print("formateados:::", formateados)
    if not formateados['Estado']:
        return formateados

    # formateados = formateados['Dato']
    #
    # formateados.reverse()

    return respuesta(True, u'Se monitoreo el chat con exito', dato=formateados['Dato'])


# dos en -1
# formatear_mensajes2

def extraer_mensajes(driver, cantidad):
    """
    devuelve los ultimos mensajes del chat. segun la cantidad
    :param driver:
    :param cantidad: numero de mensajes a leer
    :return:
    """
    return driver.find_elements_by_css_selector(ids['chat']['mensajes_entrantes'])[-cantidad:]
