import struct
import time
from datetime import datetime
import socket
import logging

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

class CircutorReader:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.socket = None
        self.direccion = 1
        self.estado = 1
        self.valoresInstantaneos = {
            'E': {'activaImpo': 0, 'activaExpo': 0, 'reactivaQ1': 0, 'reactivaQ2': 0, 'reactivaQ3': 0, 'reactivaQ4': 0, 'tiempo': None},
            'P': {'activaTotal': 0, 'reactivaTotal': 0, 'factorPotenciaTotal': 0, 'activaFases': [0, 0, 0], 'reactivaFases': [0, 0, 0], 'factorPotenciaFases': [0, 0, 0], 'tiempo': None},
            'IV': {'tension': [0, 0, 0], 'corriente': [0, 0, 0], 'tiempo': None}
        }

    def connect(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((self.ip, self.port))
        logging.info(f"Conectado a {self.ip}:{self.port}")

    def disconnect(self):
        if self.socket:
            self.socket.close()
            logging.info("Desconectado")

    def imprimir_valores(self):
        print("\n--- Valores Instantáneos ---")
        for grupo, datos in self.valoresInstantaneos.items():
            print(f"\nGrupo {grupo}:")
            for clave, valor in datos.items():
                if clave == 'tiempo':
                    if valor:
                        print(f"  Timestamp: {valor}")
                elif isinstance(valor, list):
                    print(f"  {clave}: {', '.join([f'{v:.2f}' for v in valor])}")
                else:
                    print(f"  {clave}: {valor:.2f}")

    def procesar_etiqueta_tiempo(self, timestamp_bytes):
        try:
            minutos = timestamp_bytes[0] & 0x3F
            hora = timestamp_bytes[1] & 0x1F
            dia = timestamp_bytes[2] & 0x1F
            mes = timestamp_bytes[3] & 0x0F
            ano = 2000 + (timestamp_bytes[3] >> 4) + ((timestamp_bytes[4] & 0x7F) << 4)
            return datetime(ano, mes, dia, hora, minutos)
        except Exception as e:
            logging.error(f"Error al procesar etiqueta de tiempo: {str(e)}")
            return None

    def procesar_grupo_192(self, mensajeTX):
        try:
            data_start = 12
            self.valoresInstantaneos['E']['activaImpo'] = int.from_bytes(mensajeTX[data_start:data_start+4], 'little') / 1000.0
            self.valoresInstantaneos['E']['activaExpo'] = int.from_bytes(mensajeTX[data_start+4:data_start+8], 'little') / 1000.0
            self.valoresInstantaneos['E']['reactivaQ1'] = int.from_bytes(mensajeTX[data_start+8:data_start+12], 'little') / 1000.0
            self.valoresInstantaneos['E']['reactivaQ2'] = int.from_bytes(mensajeTX[data_start+12:data_start+16], 'little') / 1000.0
            self.valoresInstantaneos['E']['reactivaQ3'] = int.from_bytes(mensajeTX[data_start+16:data_start+20], 'little') / 1000.0
            self.valoresInstantaneos['E']['reactivaQ4'] = int.from_bytes(mensajeTX[data_start+20:data_start+24], 'little') / 1000.0
            timestamp_bytes = mensajeTX[-7:-2]
            self.valoresInstantaneos['E']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)
            self.imprimir_valores()
        except Exception as e:
            logging.error(f"Error al procesar grupo 192: {str(e)}")

    def procesar_grupo_193(self, mensajeTX):
        try:
            data_start = 12
            self.valoresInstantaneos['P']['activaTotal'] = int.from_bytes(mensajeTX[data_start:data_start+3], 'little') / 10.0
            self.valoresInstantaneos['P']['reactivaTotal'] = int.from_bytes(mensajeTX[data_start+3:data_start+6], 'little') / 10.0
            self.valoresInstantaneos['P']['factorPotenciaTotal'] = int.from_bytes(mensajeTX[data_start+6:data_start+8], 'little') / 1000.0
            for i in range(3):
                start = data_start + 8 + i * 9
                self.valoresInstantaneos['P']['activaFases'][i] = int.from_bytes(mensajeTX[start:start+3], 'little') / 10.0
                self.valoresInstantaneos['P']['reactivaFases'][i] = int.from_bytes(mensajeTX[start+3:start+6], 'little') / 10.0
                self.valoresInstantaneos['P']['factorPotenciaFases'][i] = int.from_bytes(mensajeTX[start+6:start+8], 'little') / 1000.0
            timestamp_bytes = mensajeTX[-7:-2]
            self.valoresInstantaneos['P']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)
            self.imprimir_valores()
        except Exception as e:
            logging.error(f"Error al procesar grupo 193: {str(e)}")

    def procesar_grupo_194(self, mensajeTX):
        try:
            data_start = 12
            for i in range(3):
                corriente = int.from_bytes(mensajeTX[data_start + i*6:data_start + i*6 + 3], 'little') / 100.0
                tension = int.from_bytes(mensajeTX[data_start + i*6 + 3:data_start + i*6 + 6], 'little') / 10.0
                self.valoresInstantaneos['IV']['corriente'][i] = corriente
                self.valoresInstantaneos['IV']['tension'][i] = tension
            timestamp_bytes = mensajeTX[-7:-2]
            self.valoresInstantaneos['IV']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)
            self.imprimir_valores()
        except Exception as e:
            logging.error(f"Error al procesar grupo 194: {str(e)}")

    def send_receive(self, data, expected_response=None):
        logging.debug(f"Enviando: {data.hex()}")
        self.socket.sendall(data)
        response = self.socket.recv(1024)
        logging.debug(f"Recibido: {response.hex()}")
        if expected_response is not None and response[1] != expected_response:
            logging.warning(f"Respuesta inesperada: {response[1]}, esperada: {expected_response}")
        return response

    def create_fixed_frame(self, control):
        msg = bytearray([0x10, control, self.direccion & 0xFF, self.direccion >> 8])
        crc = self.CRC8(msg[1:])
        msg.extend([crc, 0x16])
        return msg

    def create_auth_request(self):
        msg = bytearray([0x68, 0x0D, 0x0D, 0x68, 0x73, self.direccion & 0xFF, self.direccion >> 8])
        msg.extend([0xB7, 0x01, 0x06, 0x01, 0x00, 0x00])
        msg.extend(struct.pack('<I', 1))  # Clave de acceso (ajustar según sea necesario)
        crc = self.CRC8(msg[4:])
        msg.extend([crc, 0x16])
        return msg

    def autenticarMedidor(self):
        try:
            init_request = self.create_fixed_frame(0x49)
            response = self.send_receive(init_request, 0x0B)
            logging.info("Paso 1 completado")

            reset_request = self.create_fixed_frame(0x40)
            response = self.send_receive(reset_request, 0x00)
            logging.info("Paso 2 completado")

            status_request = self.create_fixed_frame(0x49)
            response = self.send_receive(status_request, 0x0B)
            logging.info("Paso 3 completado")

            auth_request = self.create_auth_request()
            response = self.send_receive(auth_request, 0x00)
            logging.info("Paso 4 completado")

            confirm_request = self.create_fixed_frame(0x5B)
            response = self.send_receive(confirm_request)
            logging.info("Paso 5 completado")

            if response[0] == 0x68 and response[7] == 0xB7 and response[9] == 0x07:
                logging.info("Autenticación completada con éxito")
                return True
            else:
                logging.error("Confirmación de autenticación fallida")
                return False
        except Exception as e:
            logging.error(f"Error de autenticación: {e}")
            return False

    def leerInstantaneos(self, grupo):
        try:
            if not self.autenticarMedidor():
                return "AUTENTICACION_FALLIDA"

            request = self.create_request(grupo)
            response = self.send_receive(request)
            if response[0] == 0x10 and response[1] == 0x00:
                logging.warning(f"Respuesta ACK recibida para el grupo {grupo}. Intentando de nuevo.")
                time.sleep(1)
                response = self.send_receive(self.create_fixed_frame(0x5B))

            if grupo == 192:
                self.procesar_grupo_192(response)
            elif grupo == 193:
                self.procesar_grupo_193(response)
            elif grupo == 194:
                self.procesar_grupo_194(response)

            return "COMUNICACION_EXITOSA"
        except Exception as e:
            logging.error(f"Error en leerInstantaneos: {str(e)}")
            return "ERROR_TRY_CATCH"

    def create_request(self, group):
        msg = bytearray([0x68, 0x0A, 0x0A, 0x68, 0x73, self.direccion & 0xFF, self.direccion >> 8])
        msg.extend([0xA2, 0x01, 0x05, 0x01, 0x00, 0x00, group])
        crc = self.CRC8(msg[4:])
        msg.extend([crc, 0x16])
        return msg

    def CRC8(self, data):
        crc = 0
        for byte in data:
            crc += byte
        return crc % 256

# Ejemplo de uso
if __name__ == "__main__":
    reader = CircutorReader("35.247.249.153", 58111)
    try:
        reader.connect()
        for grupo in [192, 193, 194]:
            try:
                result = reader.leerInstantaneos(grupo)
                print(f"\nResultado para grupo {grupo}: {result}")
                if result != "COMUNICACION_EXITOSA":
                    print(f"Error al leer el grupo {grupo}: {result}")
                else:
                    print(f"Lectura exitosa del grupo {grupo}")
            except Exception as e:
                print(f"Error al leer el grupo {grupo}: {str(e)}")
            time.sleep(2)
    except Exception as e:
        print(f"Error general: {str(e)}")
    finally:
        reader.disconnect()