import json
from datetime import datetime


class Estacion():
    
    def __init__(self, name, db_collection ):
        
        self.name               = name      # Índice de la estación
        
        self.new_data           = False  
        self.cloud_data         = False
        
        # * Atributos para DB
        self.db_collection      = db_collection
        self.data_dict = {                              # Diccionario con datos de sensores, usado para guardar en DB y reiniciado tras el proceso
            'Port 2': False,
            'Port 3': False,
            'timeStamp': 0
        }       
        
        # * Atributos para FrontEnd
        self.latest_sensors_data_dict      = {         # Diccionario con datos de sensores, copia de data_dict pero no es reiniciado, usado para envío de datos a servidor remoto
            "batteryPercent"    : '',   
            # Profundidad 1
            "prof1Temperature"  : '',   
            "salinity1Ppt"      : '',   
            "do1MgPerL"         : '',
            "do1Percent"        : '',
            # Profundidad 2
            "prof2Temperature"  : '',   
            "salinity2Ppt"      : '',   
            "do2MgPerL"         : '',
            "do2Percent"        : '',
            # Profundidad 3
            "prof3Temperature"  : '',   
            "salinity3Ppt"      : '',   
            "do3MgPerL"         : '',
            "do3Percent"        : ''
        }

        self._server_data_dict = {}         # Datos formateados para enviarse a servidor en la nube
    
    def update_received_data( self, received_json ):
        
        # * Almacenar el diccionario recibido como json_dict
        json_dict = received_json
        
        
        # * Guardar datos en DB cuando se obtenga un timeStamp diferente
        if json_dict["uplink_message"]["decoded_payload"]['timeStamp'] != self.data_dict['timeStamp'] and self.data_dict['timeStamp'] != 0:
            # Sólo en caso de que se tengan datos
            if len( self.data_dict.keys() ) > 3:
                self._save_data_dict_on_DB()
        
        
        # * Guardar datos localmente
        self.data_dict.update( self._read_received_json( received_json, json_dict ) )
        
        
        # * Guardar datos en DB cuando se reciban los datos de ambos puertos
        if self.data_dict['Port 2'] and self.data_dict['Port 3']:
            self._save_data_dict_on_DB()
        
        return
            
        
    def _read_received_json(self, received_json, json_dict ):
        # ? Retorna variable data_dict con los datos recibidos ordenados, se utiliza para actualizar variable local self.data_dict

        # * Obtener y Formatear la fecha y hora en cadenas
        fecha_hora_actual   = datetime.now()
        fecha_actual_str    = fecha_hora_actual.strftime("%Y-%m-%d")
        hora_actual_str     = fecha_hora_actual.strftime("%H:%M:%S")
        formatted_time      = fecha_hora_actual.strftime("%d-%m-%Y %H:%M:%S")
        
        
        # * Leer parámetros
        
        # Inicializar variables
        end_device_ids      = json_dict.get("end_device_ids", {})
        uplink_message      = json_dict.get("uplink_message", {})
        decoded_payload     = uplink_message.get("decoded_payload", {})
        rx_metadata         = uplink_message.get("rx_metadata", [{}])
        settings            = uplink_message.get("settings", {})
        data_rate           = settings.get("data_rate", {})
        lora                = data_rate.get("lora", {})
        
        # Leer parámetros básicos
        
        device_id           = end_device_ids.get("device_id")
        device_eui          = end_device_ids.get("dev_eui")
        port                = uplink_message.get("f_port")
        timeStamp           = decoded_payload.get("timeStamp")
        snr                 = rx_metadata[0].get("snr")
        rssi                = rx_metadata[0].get("rssi")
        channel_rssi        = rx_metadata[0].get("channel_rssi")
        spreading_factor    = lora.get("spreading_factor")
        f_cnt               = uplink_message.get("f_cnt")
        
        uplink_message_str = json.dumps(received_json.get("uplink_message"))
        
        # * Crear el diccionario data_dict
        data_dict = {
            "fecha": fecha_actual_str,
            "hora": hora_actual_str,
            "dif_last": 0,
            "device_id": device_id,
            "dev_eui": device_eui,
            "f_port": port,
            "snr": snr,
            "rssi": rssi,
            "channel_rssi": channel_rssi,
            "spreading_factor": spreading_factor,
            "f_cnt": f_cnt,
            "uplink_message": uplink_message_str,
            "timeStamp": timeStamp,
            "formatted_time": formatted_time,
        }
        
        # * Añadir campos por puerto
        if port == 2:
            data_dict['batteryPercent']     = decoded_payload.get("batteryPercent")
            data_dict['Port 2']             = True
            
            campos_adicionales              = ["prof1Temperature", "salinity1Ppt", "do1MgPerL", "do1Percent", "arrayProfs" ]
            
        else:
            data_dict['batteryVoltageMv']   = decoded_payload.get("batteryVoltageMv")
            data_dict['flags']              = self._flag_to_bits( int( decoded_payload.get("Flags") ) )
            data_dict['Port 3']             = True
            
            campos_adicionales              = [
                                                "prof2Temperature", "salinity2Ppt", "do2MgPerL", "do2Percent",
                                                "prof3Temperature", "salinity3Ppt", "do3MgPerL", "do3Percent"
                                            ]
            
            
        # * Recorrer la lista de campos adicionales y asignar sus valores al diccionario data_dict
        # Deprecado, llenado de graphData
        for campo in campos_adicionales:
            try:
                value = decoded_payload.get(campo)
                # Añadir a data_dict:
                data_dict[campo] = value    # json_dict[campo]
                
                
            except Exception as e: print(f'Error con campo {campo}, {e} ')
        
        # Actualizar datos de latest_sensors_data_dict
        self.latest_sensors_data_dict.update( data_dict )
        # print(f'graphData: {self.graphData}')
        
        # * Devolver el diccionario data_dict con todos los campos guardados
        sorted_data_dict = {k: data_dict[k] for k in sorted(data_dict)}
        
        return sorted_data_dict # data_dict

    def _save_data_dict_on_DB( self ):
        """
            Guarda el diccionario self.data_dict en la base de datos MongoDB. Luego vacía el diccionario
        """ 
        # Enciende flag de nuevo dato al guardar en DB
        self.new_data = True
        # * Guardar en BD
        try:
            # Extraer el campo formatted_time y dividirlo en fecha y hora
            formatted_time = self.data_dict.get('formatted_time', '')
            fecha, hora = formatted_time.split(' ')
            
            # Preparar el documento para insertar en el array del día específico
            to_insert = {
                'Time': hora,
                'Sensores': {key: val for key, val in self.data_dict.items() if key != 'formatted_time'}
            }
            
            # Consultar si el documento del día ya existe
            day_document = self.db_collection.find_one({'dia': fecha})
            
            if day_document:
                # Si existe, actualizar el documento agregando el nuevo registro al array
                self.db_collection.update_one(
                    {'dia': fecha},
                    {'$push': {'registros': to_insert}}
                )
            else:
                # Si no existe, crear un nuevo documento para el día con el primer registro en el array
                self.db_collection.insert_one(
                    {'dia': fecha, 'registros': [to_insert]}
                )
            print(f'Dato de tarjeta {self.name} guardado')
        except Exception as e:
            # En caso de error, imprimir el mensaje de error y retornar False
            print(f"Error al guardar en la BD: {e}")
            return False
        
        
        # * Reiniciar variable local
        
        last_timeStamp = self.data_dict.get('timeStamp', 0)
        self.data_dict = {
            'Port 2': False,
            'Port 3': False,
            'timeStamp': last_timeStamp
        }
        
        return

    def update_server_data_dict( self , new_server_data_dict):
        self._server_data_dict.update( new_server_data_dict )
        self.cloud_data = True
        return
    
    def server_data_dict( self ):
        to_return = self._server_data_dict
        # self._server_data_dict = {}
        return {k: to_return[k] for k in sorted(to_return)}
      
    def _flag_to_bits( self, flag ):
        """
        Convierte un número entero a una representación binaria de 16 bits.
        # * Estructura de bytes a enviar (de izquierda a derecha)
            1ER BYTE
                do1,sal1, do2,salinity2 ,d0o3.salinity3, monitoreoOk, controlOk
                0    1    2     3         4     5            6         7
    
            2DO BYTE
                ds3231_ok, FirstWake, Libre, Libre, Libre, Libre, Libre, Libre
                0             1         2    3       4      5      6      7   
        """

        return format(flag, '016b')