


# Rerportería - Excel 
import openpyxl
from openpyxl.utils import get_column_letter
from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
from openpyxl.utils import get_column_letter, column_index_from_string
from openpyxl.chart import LineChart, Reference
from openpyxl.drawing.image import Image as ExcelImg
from openpyxl.drawing.text import CharacterProperties



from pymongo import MongoClient
from datetime import datetime, timedelta
from collections import defaultdict


# from .nanoox_timeseries import get_control_by_centro_id


import os
import sys
sys.path.insert(0, './')







class SimpleLogger:
    def info(self, msg):
        # print(f"[INFO] {msg}")
        print(msg)
    def warning(self, msg):
        # print(f"[WARNING] {msg}")
        print(msg)
    def error(self, msg):
        print(f"[ERROR] {msg}")
    
    def debug(self, msg):
        # print(f"[DEBUG] {msg}")
        print(msg)

logger = SimpleLogger()


from datetime import datetime, timedelta, timezone
def obtener_datos_por_fecha_por_alias(db, fecha_str,  centro_id=None):    
    
    print(fecha_str)
    try:
        
        db = client["NANOOX_SERVER_OLD_TS"]
        # # Conectar a las bases seleccionadas        
        metadata_col = db["metadata"]
        sensores_ts_col = db["sensores_timeseries"]

        # print(metadata_col)
        

        # Parsear la fecha en formato 'YYYY-MM-DD'
        # Parsear fecha como UTC
        fecha = datetime.strptime(fecha_str, "%Y-%m-%d").replace()
        siguiente_dia = fecha + timedelta(days=1)


        # Buscar bombas
        filtro_metadata = {"tipo": "bomba"}
        if centro_id:
            filtro_metadata["centro_id"] = centro_id
        print(filtro_metadata, fecha_str)
        alias_map = {}
        for doc in metadata_col.find(filtro_metadata):
        # for doc in metadata_col.find():

            # print(f"{doc=}")
            # alias_map[doc["_id"]] = doc.get("alias", f"bomba_{doc['_id']}")
            alias_map[doc["id"]] = doc.get("alias", f"bomba_{doc['id']}")

        # print(f"{alias_map=}")


        # Filtro series de tiempo
        filtro_ts = {
            "bomba_id": {"$in": list(alias_map.keys())},
            "timestamp": {"$gte": fecha, "$lt": siguiente_dia}
        }
        if centro_id:
            filtro_ts["centro_id"] = centro_id

        # Campos a extraer
        campos = {
            "_id": 0,
            "bomba_id": 1,
            "timestamp": 1,
            "O2": 1,
            "SO2": 1,
            "TEMP": 1,
            "RLY": 1,
            "InStart": 1,
            "Control": 1,
            "Presion": 1
        }

        resultados = defaultdict(list)
        for doc in sensores_ts_col.find(filtro_ts, campos):
            alias = alias_map.get(doc["bomba_id"], str(doc["bomba_id"]))
            doc.pop("bomba_id")
            resultados[alias].append(doc)

        # print(resultados)
        return dict(resultados)
    except Exception as e:
        logger.error(f"{e.__traceback__.tb_lineno} {e}")













def add_markwater_images(worksheet, img_path, start_column="C", start_row=21, repeats=4, step=7):
    """
    Inserta repetidamente la imagen en la hoja, avanzando horizontalmente
    columnas que pueden exceder la 'Z' (soporta AA, AB, etc.).
    
    :param worksheet: Hoja de cálculo (openpyxl worksheet)
    :param img_path: Ruta al archivo de imagen
    :param start_column: Columna inicial (string), p.ej "C"
    :param start_row: Fila inicial (int)
    :param repeats: Cuántas veces quieres repetir la imagen
    :param step: Cuántas columnas saltar cada vez (int)
    """
    # 1) Convertir la columna inicial a índice numérico (A=1, B=2, C=3, ... AA=27, etc.)
    start_col_num = column_index_from_string(start_column)

    for i in range(repeats):
        # 2) Cálculo de la siguiente columna
        col_index = start_col_num + i * step
        # Opcional: verificar que col_index no exceda el máximo de Excel (16384 columnas en XLSX)
        if col_index > 16384:
            print(f"La columna calculada ({col_index}) excede el límite de Excel (XFD). Deteniendo.")
            break
        if col_index < 1:
            print(f"Índice de columna no válido ({col_index}). Se ignora.")
            continue

        # 3) Convertir el índice numérico a la correspondiente letra(s) de columna
        new_col_str = get_column_letter(col_index)
        
        # 4) Construir la posición en la hoja (ej. "C21", "J21", "AA21", etc.)
        new_position = f"{new_col_str}{start_row}"

        # 5) Crear la imagen y setear tamaño
        img_copy = ExcelImg(img_path)
        img_copy.width = 600
        img_copy.height = 450

        # 6) Insertar la imagen en la hoja
        worksheet.add_image(img_copy, new_position)
        print(f"Imagen insertada en {new_position}")

    print("Marca de agua agregada (con expansión de columnas) satisfactoriamente.")



def _gen_data_report(db, rango_fechas,  excel_save_path, file_name, centro_id = None):

    print(f"_gen_data_report {centro_id=}")    
    

    # CONTROL = get_control_by_centro_id(db, centro_id)
    CONTROL = "PS"

    try:

        logger.info(f"generando reporte de tipo <CONTROL>")    
                            
        logger.debug(rango_fechas)        
        
        # Supongamos que 'msg' es un diccionario que contiene las claves 'FechaInicio' y 'FechaFin'

        # Convertir las fechas de cadena a objetos datetime
        start = datetime.strptime(rango_fechas['startDate'], "%d/%m/%Y")
        end = datetime.strptime(rango_fechas['endDate'], "%d/%m/%Y")

        
        
        #-- Calculo de cantidad de dias a analizar --#
        if len( str(end-start).split(" ") ) > 1:
            diasEntre = int( str(end-start).split(" ")[0] ) + 1 # para que tome en cuenta ultimo dia
            print("Se han seleccionado ",diasEntre," dias")
        else:
            diasEntre = 1
            


        #* -- Crear libro nuevo en Excel-- #
        workbook = openpyxl.Workbook()
        ws_names = []


        fechas = []

        #-- Analisis por dia entre fechas del rango --#
        
        logger.debug(f"{diasEntre=}")
        logger.info("Comenzando a generar reporte...")

        for i in range(diasEntre):            
            
            dia = str(start + timedelta(days=i)).split(" ")[0].split("-")
            fecha_format = "-".join(dia)  
            fechas.append(fecha_format)  

            # print(f"{fecha_format=}")
            siguiente_dia = start + timedelta(days= i)

            # print(f"{start=}  {siguiente_dia=}" )


            datos_sensores_bombas = obtener_datos_por_fecha_por_alias(db, fecha_format, centro_id=centro_id)
            # print(datos_sensores_bombas)

            
            if datos_sensores_bombas:
                print(f"Existen datos en la fecha {fecha_format}")
                # print(datos_sensores_bombas)
                ...                                

            #     # Nombres de cámaras
                bomb_names = list(datos_sensores_bombas.keys())     
                bomb_names.sort()
                print(f"{bomb_names=}")


                rangos_iny_on = {alias_bomba: []   for alias_bomba in bomb_names }   
                rangos_bomba_on = {alias_bomba: []   for alias_bomba in bomb_names }   
       
                
                #cantidad de elementos por columna
                max_column = len(datos_sensores_bombas[bomb_names[0]])

                #Creacion de pagina especifica para la fecha analizada
                worksheet = workbook.create_sheet(fecha_format)
                ws_names.append(fecha_format)

                #* ----- especificaciones para paginas en el excel ---- *
                v_offset =     16                       #espacio vertical desde el inicio de la pagina        
                h_offset =     2                        #espacio horizontal desde el inicio de la pagina
                tablas_dist =  6                        #espacio entre tablas
                tablas_space = [""]*(tablas_dist - 5)

                
                cant_keys_tabla = 5 #keys interes  (Hora , O2, SO2, TEMP, Presion)          "_id": 0,

                # print(f"{KEYS_INTERES=}")
                space_titles = cant_keys_tabla   - h_offset - 1   
                #* -------------------------------------------------------



                #* -------------------------------------------- Agregar filas --------------------------------------- #
                for i in range(v_offset):        
                    worksheet.append([]) # Dejar <v_offset> celdas vacias al inicio


                #* -- Primera Fila : Nombres de las estanques -- #
                fila = []
                for alias_bomba  in bomb_names:   #en formato fila para facilitar visualizacion
                    fila+= [""]*h_offset ;  fila.append(f"Bomba {alias_bomba}")   ; fila+= [""]*space_titles 
                    fila += tablas_space
                                
                worksheet.append(fila)


                #*  -- Segunda Fila : información de las jaulas -- #
                estructura = ([""]*h_offset + ["Hora" ,"Oxígeno mg/L",  "Saturación %" , 'Temperatura °C', "Presión bar"] )
                estructura += ([""]*(h_offset-1) + ["Hora" ,"Oxígeno mg/L",  "Saturación %" , 'Temperatura °C', "Presión bar"] )*(len(bomb_names)-1)
                # print(estructura)
                worksheet.append( estructura )




            #     #* -- Filas siguientes: -- #
            #     #* Agrega iterativamente los datos directamente desde la base de datos
                
                Rangos = {}                        
                # Rango de celdas de cada canal
                for idx,channel in enumerate(bomb_names):            
                    
                    cell_inicial_Letra = get_column_letter( idx*tablas_dist + h_offset +1 ) # El ultimo +1 es por desplazamiento lateral de las tablas
                    cell_final_Letra = get_column_letter(   idx*tablas_dist + h_offset + cant_keys_tabla )
                    

                    cell_graph = get_column_letter( idx*tablas_dist + h_offset   +1)            
                    
                    Rangos[channel] = {
                                        'L_Inicial':cell_inicial_Letra, 
                                        'L_Final': cell_final_Letra,
                                        'L_Graph': cell_graph,
                                        
                                        'celdaInicial': str(cell_inicial_Letra + str(2 + v_offset)), 
                                        'celdaFinal': str(cell_final_Letra + str( len(datos_sensores_bombas[channel]) + 2 + v_offset ) ),                                    
                                    }
                    


                # print(f"{Rangos=}")

                values_for_charts = {}
                for i in range(max_column):                
                    nueva_fila = []                                
                                
                    # Para cada dato -> recorrer todos los canales
                    for idx,channel in enumerate(bomb_names):
                        if len(datos_sensores_bombas[channel]) > i:
                                                                                         
                            data_fila = datos_sensores_bombas[channel][i]
                            # print(f"{data_fila=}")
                            ts =  data_fila["timestamp"].strftime("%H:%M:%S")  
                            ts_obj = data_fila["timestamp"]
                            
                            # Convertir horas a formato datetime si es necesario
                                                              
                #         #busca instantes de inyeccion
                            if data_fila.pop("RLY") == True:
                                rangos_iny_on[channel].append(ts)
                                # print(f"{data_fila=}")

                            if CONTROL == "VDF":
                                if data_fila.pop("InStart") == "01":                        
                                    # print(f"{data_fila=}")
                                    rangos_bomba_on[channel].append(ts)
                            elif CONTROL == "PS":                        
                                if data_fila.pop("InStart") == "00aa":
                                    # print(f"{data_fila=}")
                                    rangos_bomba_on[channel].append(ts)


                            

                            nueva_fila.append( '' ) 
                            nueva_fila.append( '' )

                            nueva_fila+= [ts, data_fila["O2"], data_fila["SO2"], data_fila["TEMP"], data_fila["Presion"]  if  data_fila["Presion"] > -0.5 else None ]                      
                            nueva_fila += tablas_space[0:-h_offset]

                            if channel not in values_for_charts:
                                values_for_charts[channel] = {"ts": [], "O2": [], "SO2": []}
                            else:
                                if  isinstance(data_fila["O2"] , float):
                                    values_for_charts[channel]["ts"].append(ts_obj)
                                    values_for_charts[channel]["O2"].append(data_fila["O2"])
                                    values_for_charts[channel]["SO2"].append(data_fila["SO2"])
                        

                    # print(nueva_fila)
                    # # Agregar a la planilla la fila con todas las columnas
                    worksheet.append( nueva_fila )
                # print(f"{values_for_charts=}")
 
            # #* --------------------------------------------  Graficos --------------------------------------- #      
                
                from io import BytesIO
                import matplotlib
                matplotlib.use('Agg')  # Backend sin interfaz gráfica                
                import matplotlib.pyplot as plt
                import matplotlib.dates as mdates
                from openpyxl.drawing.image import Image as ExcelImg
                

                plt.rcParams.update({
                    'font.size': 22,          # Tamaño general del texto
                    'axes.titlesize': 25,     # Tamaño del título del gráfico
                    # 'axes.labelsize': 25,     # Tamaño de etiquetas de los ejes
                    'xtick.labelsize': 20,    # Tamaño de las etiquetas del eje X
                    'ytick.labelsize': 20,    # Tamaño de las etiquetas del eje Y
                    'legend.fontsize': 25,    # Tamaño de la leyenda
                    'figure.titlesize': 25    # Tamaño del título de la figura
                })


               # Recorremos los datos por canal
                for idx, channel_data in enumerate(values_for_charts):
                    ...
                    
                    # Convertir a formato numérico para matplotlib
                    x = mdates.date2num(values_for_charts[channel_data]["ts"])
                    o2 = values_for_charts[channel_data]["O2"]
                    so2 = values_for_charts[channel_data]["SO2"]
                    
                    # Dimensiones del gráfico
                    ancho_grafico = 14  # Anchura en pulgadas
                    alto_grafico = 7.3  # Altura en pulgadas
                    try:

                        # Crear gráfico con eje principal
                        fig, ax1 = plt.subplots(figsize=(ancho_grafico, alto_grafico), dpi=100)
                        
                        # Graficar Nivel de O2 en eje principal (izquierdo)
                        ax1.plot(x, o2, label="Nivel de O2 (mg/L)", color="#1e4882", linewidth=2)
                        try:
                            ax1.set_xlim(min(x), max(x))
                        except:
                            pass
                        ax1.set_xlabel("Hora del día", fontsize=16)
                        ax1.set_ylabel("Nivel de O2 (mg/L)", color="#1e4882", fontsize=16)
                        ax1.set_ylim(0,12)
                        ax1.tick_params(axis='y', labelcolor="#1e4882")

                        # Crear segundo eje Y para saturación de oxígeno
                        ax2 = ax1.twinx()
                        try:
                            ax2.set_xlim(min(x), max(x))
                        except:
                            pass
                        ax2.plot(x, so2, label="Saturación %", color="#ab0202", linestyle="--", linewidth=2)
                        try:
                            ax2.set_ylim(0,max(so2)+10)
                        except:
                            pass
                        ax2.set_ylabel("Saturación %", color="#ab0202", fontsize=16)
                        ax2.tick_params(axis='y', labelcolor="#ab0202")

                        # Configuración del eje X en escala de horas
                        interval = 60
                        ax1.xaxis.set_major_locator(mdates.MinuteLocator(interval=interval))
                        ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))

                        # Rotar etiquetas del eje X
                        fig.autofmt_xdate()

                        # Título del gráfico
                        ax1.set_title(f"Sensor {channel_data}", fontsize=14)

                        # Leyendas combinadas
                        lines_1, labels_1 = ax1.get_legend_handles_labels()
                        lines_2, labels_2 = ax2.get_legend_handles_labels()
                        ax1.legend(lines_1 + lines_2, labels_1 + labels_2, loc='lower center', bbox_to_anchor=(0.5, -0.25), ncol=2)

                        # Ajustar diseño
                        plt.tight_layout(pad=0.5)
                        plt.grid(True)

                        # Guardar el gráfico en un buffer en memoria
                        img_data = BytesIO()
                        plt.savefig(img_data, format='png', dpi=100, bbox_inches='tight', pad_inches=0.1)
                        plt.close(fig)

                        # Insertar la imagen en la hoja de Excel
                        img_data.seek(0)
                        img = ExcelImg(img_data)

                        # Configurar tamaño de imagen
                        img.width = 480
                        img.height = 292

                        celda = Rangos[channel_data]["L_Graph"] + "2"
                        worksheet.add_image(img, celda)
                    except Exception as e:
                        print(f"Error generando grafico en linea {e.__traceback__.tb_lineno} {e}")



            # #* --------------------------------------------  Estilos --------------------------------------- #        

                # Marca de Agua
                
                # Obtener la ruta absoluta del archivo de imagen
                try:
                    base_dir = os.path.dirname(os.path.abspath(__file__))  # Directorio actual del script (database)
                    img_path = os.path.join(base_dir, "images", "Logo_Reportes.png")  # Subir un nivel y entrar a 'images'

                    # img_path = os.path.join(os.path.dirname(__file__), 'Logo_Reportes.png')  # Ruta relativa al script                     
                    add_markwater_images(worksheet, img_path,
                                         start_column="C", start_row=21,
                                         repeats=len(datos_sensores_bombas.keys()), step=7)


                except Exception as e:
                    print(f"Error al agregar la marca de agua: {e}")



                font = Font(b=True, color="FFFFFF")# color="00FFFFFF" -> blanco

                fill = PatternFill(fill_type="solid",
                        start_color='e84e0e',#'204D84',
                        end_color='e84e0e')#'204D84')
                

                
                fill_iny = PatternFill(fill_type="solid",
                        start_color='FFFF00',#'204D84',
                        end_color='FFFF00')#'204D84')
                
                
                fill_vdf = PatternFill(fill_type="solid",
                        start_color='C8C8C8',#'204D84',
                        end_color='C8C8C8')#'204D84')
                

                    # Estilo de bordes: 
                border = Border(left=Side(border_style="thin", color="000000"),
                            right=Side(border_style="thin", color="000000"),
                            top=Side(border_style="thin", color="000000"),
                            bottom=Side(border_style="thin", color="000000"))

                for idx,channel in enumerate(bomb_names):
                        
                    Letra1 = get_column_letter( idx*tablas_dist + h_offset + 1 ) # El  +1 es por desplazamiento lateral de las tablas
                    Letra2 = get_column_letter( idx*tablas_dist + h_offset + 2 )
                    Letra3 = get_column_letter( idx*tablas_dist + h_offset + 3 )
                    Letra4 = get_column_letter( idx*tablas_dist + h_offset + 4 )
                    Letra5 = get_column_letter( idx*tablas_dist + h_offset + 5 )

                    #--  Diseño celdas  -- #
                    cell_merge  = worksheet[Letra1 + str(v_offset +1)]
                    cell_merge.font  = font  
                    cell_merge.fill  = fill 


                    # Definicion celdas
                    cell_1 = worksheet[Letra1 + str(v_offset + 2)]  
                    cell_2 = worksheet[Letra2 + str( v_offset +2 )]  
                    cell_3 = worksheet[Letra3 + str( v_offset +2 )]                            
                    cell_4 = worksheet[Letra4 + str( v_offset +2 )]   
                    cell_5 = worksheet[Letra5 + str( v_offset +2 )]   
                        
                    # Font:
                    cell_1.font  = font  
                    cell_2.font  = font  
                    cell_3.font  = font 
                    cell_4.font  = font
                    cell_5.font  = font
                    
                    # Fill:
                    cell_1.fill  = fill  
                    cell_2.fill  = fill 
                    cell_3.fill  = fill    
                    cell_4.fill  = fill    
                    cell_5.fill  = fill       
                    
                        

                #-- Combinar cheldas de titulos --#
                for column in range( len(bomb_names) ):
                    worksheet.merge_cells(start_row=v_offset + 1, 
                                        start_column=column*tablas_dist + h_offset +1, 
                                        end_row=v_offset +1, 
                                        end_column=column*tablas_dist + h_offset +cant_keys_tabla
                                        ) # fila 2 ya que se deja una vacía al inicio
                

                #-- Aplicar el borde a un conjunto de celdas --#
                for channel in bomb_names:
                    cell_range = worksheet[Rangos[channel]['celdaInicial']  : Rangos[channel]['celdaFinal']   ]
                    for row in cell_range:
                        for cell in row:
                            cell.border = border
                        
                            if row[0].value in rangos_iny_on[channel] and  row[0].value in rangos_bomba_on[channel]:
                                
                                cell.fill = fill_iny

                            elif row[0].value in rangos_bomba_on[channel]:
                                cell.fill = fill_vdf
                            


                #-- alineacion de celdas --
                for row  in worksheet.iter_rows(min_row=4, max_row= max_column + v_offset + 2):
                    for cell in row:
                        cell.alignment = Alignment(horizontal='center', 
                                                vertical='center', 
                                                shrink_to_fit=True,  
                                                wrap_text=False)
                        
                
                # Ajustar el ancho de las columnas solo para aquellas que contienen datos
                for column in worksheet.columns:
                    max_length = 0
                    for cell in column:
                        try:
                            if cell.value is not None and len(str(cell.value)) > max_length:
                                max_length = len(cell.value)
                        except:
                            pass
                    adjusted_width = (max_length + 2)
                    if adjusted_width > 2:  # Ajustar solo si hay datos en la columna
                        worksheet.column_dimensions[column[0].column_letter].width = adjusted_width


                password = 'itg123chile'
                # Proteger Hoja
                #Al finalizar la escritura del worksheet
                worksheet.protection.sheet = True
                worksheet.protection.objects=True
                worksheet.protection.scenarios=True
                worksheet.protection.password= password

                # * Proteger documento
                #Antes de guardar y cerrar el woorkbook
                workbook.security.workbookPassword = password 
                workbook.security.lockStructure =True             
            else:
                print("No hay datos para esta fecha")
        if 'Sheet' in workbook.sheetnames:
            workbook.remove(workbook['Sheet'])
                            
        try:
            
             
            # Crear la ruta completa para el archivo Excel
            print(f"{rango_fechas=}")
            print(f"{fecha_format=}")
            print(fechas, fechas[0], fechas[-1])
            identifier = f"{fechas[0]}_{fechas[-1]}" if fechas[0] != fechas[-1]  else f"{fechas[0]}"

            dest_path = os.path.join(f"Reporte_{identifier}.xlsx"
            # dest_path = os.path.join(path_escritorio, f"Reporte_{identifier}.xlsx")
                                     )
            dest_path = os.path.join(excel_save_path, file_name)
            print(f"DEBUG: A punto de guardar el Workbook en {dest_path}")
            # Verificar que la ruta exista
            if not os.path.exists(excel_save_path):
                raise FileNotFoundError(f"No existe la carpeta destino: {excel_save_path}")


            


            # Guardar el archivo Excel
            workbook.save(dest_path)
            if hasattr(workbook, 'close'):
                workbook.close()

            logger.info(f"Archivo Excel guardado correctamente en: {dest_path}")

        except Exception as e:
            logger.error(f"Error al guardar el archivo: {e}")                            
             
        return True
    except Exception as e:
        logger.error(f"Error: {e} {e.__traceback__.tb_lineno}")




















def get_historial_control_as_dict():
    """
    Obtiene todos los logs de la colección 'historial_control' y los organiza en un diccionario.
    Las llaves del diccionario son las fechas (en formato 'YYYY-MM-DD') y los valores son listas de logs.

    :return: Diccionario con las fechas como llaves y listas de logs como valores.
    """
    try:
        # Conexión a la base de datos MongoDB
        db = get_db()
        collection = db['historial_control']

        # Obtener todos los documentos de la colección
        documents = collection.find({})

        # Transformar los documentos en un diccionario
        historial_dict = {}
        for doc in documents:
            date = doc.get('date')  # Obtener la fecha
            logs = doc.get('logs', [])  # Obtener los logs (por defecto, lista vacía si no existe)
            historial_dict[date] = logs

        return historial_dict
    except Exception as e:
        logger.error(f"Error al obtener el historial de 'historial_control': {e} {e.__traceback__.tb_lineno}")
        raise




def _gen_reporte_historial(rango_fechas):

    
    # Supongamos que 'msg' es un diccionario que contiene las claves 'FechaInicio' y 'FechaFin'

    # Convertir las fechas de cadena a objetos datetime
    start = datetime.strptime(rango_fechas['startDate'], "%d/%m/%Y")
    end = datetime.strptime(rango_fechas['endDate'], "%d/%m/%Y")


    #-- Calculo de cantidad de dias a analizar --#
    if len( str(end-start).split(" ") ) > 1:
        diasEntre = int( str(end-start).split(" ")[0] ) + 1 # para que tome en cuenta ultimo dia
        print("Se han seleccionado ",diasEntre," dias")
    else:
        diasEntre = 1
               

    
    try:
        #diccionario con el formato {date:  [{"hora": hour, "msg": msg}, {"hora": hour, "msg": msg}, ...] }

        dicc_logs = get_historial_control_as_dict()
                                                            
        
        
        #* ----- especificaciones para paginas en el excel ---- *
        v_offset =     2                      #espacio vertical desde el inicio de la pagina        
        h_offset =     2                        #espacio horizontal desde el inicio de la pagina
        tablas_dist =  6                        #espacio entre tablas
        tablas_space = [""]*(tablas_dist - 4)

        cant_keys_tabla = 3
        space_titles = cant_keys_tabla   - h_offset - 1   

            #* -- Crear libro nuevo en Excel-- #
        workbook = openpyxl.Workbook()
        ws_names = []


    # # #* -------------------------------------------------------


        colors = dict()
        

        Rangos = {}      
                        
        # Rango de celdas de ca in enumerate(dicc_logs):            
        for key,value in dicc_logs.items():
            cell_inicial_Letra = get_column_letter(  h_offset +1 ) # El ultimo +1 es por desplazamiento lateral de las tablas
            cell_final_Letra = get_column_letter(    h_offset + cant_keys_tabla )                                    

            cell_graph = get_column_letter( h_offset   +1)            
            
            Rangos[key] = {
                                'L_Inicial':cell_inicial_Letra, 
                                'L_Final': cell_final_Letra,
                                'L_Graph': cell_graph,
                                
                                'celdaInicial': str(cell_inicial_Letra + str(1 + v_offset)), 
                                'celdaFinal': str(cell_final_Letra + str( len(value) + 2 + v_offset ) ),                                    
                            }

            for reg in value:

                # logger.debug(f"{reg['color']} {key} {reg['msg']}")
                if key not in colors:
                    colors[key] = []

                if reg["color"] != "#FFFFFF":
                    colors[key].append(reg["color"])

                else:
                    colors[key].append("#000000")
            
        # # #-- Analisis por dia entre fechas del rango --#

        fechas = []
        for i in range(diasEntre):            
            # print("Comenzando a generar reporte...")
            
            dia = str(start + timedelta(days=i)).split(" ")[0].split("-")
            fecha_format = "-".join(dia)    
            fechas.append(fecha_format) 

            

            if fecha_format in  dicc_logs.keys() :
        #     #Creacion de pagina especifica para la fecha analizada
                worksheet = workbook.create_sheet(fecha_format)
                ws_names.append(fecha_format)

                #* -------------------------------------------- Agregar filas --------------------------------------- #
                for i in range(v_offset):        
                    worksheet.append([]) # Dejar <v_offset> celdas vacias al inicio


                #* -- Primera Fila : Nombres de las estanques -- # 
                estructura = ([""]*h_offset + ["Historial de control"])
                worksheet.append( estructura )


                        
                                        
            #     #*  -- Segunda Fila : información de las jaulas -- #
                fila= [""]*h_offset +  ["Hora","Categoría" ,"Descripción"]        
                worksheet.append(fila)
        


        #     #* -- Filas siguientes: -- #
        #     #* Agrega iterativamente los datos directamente desde la base de datos
                sheet_data_src = dicc_logs[fecha_format]  #lista de diccionarios
                # print(f"{sheet_data_src=}")
                
                #     #cantidad de elementos por columna
                max_column = len(sheet_data_src)

                for idx, log in enumerate(sheet_data_src):
                # for i in range(len(sheet_data_src)):
                    nueva_fila = []     
                    nueva_fila.append( '' ) 
                    nueva_fila.append( '' )
                    
                    try:                                            
                        msg = log.get("msg", "")

                        idx = msg.find("]")
                        ts_hh_mm_ss =  log.get("date").split(" ")[1]

                        cat  = msg[:idx+1]
                        dec = msg[idx+1:]
                        nueva_fila += [ts_hh_mm_ss, cat, dec]

                    except Exception as e:
                        logger.error(f"Error: {e}  {e.__traceback__.tb_lineno}")

                        # print(msj, e)
                        # nueva_fila += [hora, msj, msj]

                    worksheet.append( nueva_fila )


            
        
                                                                                                                                                                
                        
        #* --------------------------------------------  Estilos --------------------------------------- #        

                # Marca de Agua
                
               
                # Construir la ruta absoluta a la imagen
                try:
                    base_dir = os.path.dirname(os.path.abspath(__file__))  # Directorio actual del script (database)
                    # img_path = os.path.join(base_dir, "..", "images", "Logo_Reportes.png")  # Subir un nivel y entrar a 'images'
                    img_path = os.path.join("Logo_Reportes.png")  # Subir un nivel y entrar a 'images'


                    # Verificar si la imagen existe
                    if not os.path.exists(img_path):
                        raise FileNotFoundError(f"No se encuentra la imagen en: {img_path}")

                    # Cargar la imagen
                    img = ExcelImg(img_path)
                    img.anchor = 'C21'  # Posición inicial de la imagen en la hoja

                    # Ajustar tamaño de la imagen
                    img.width = 600  # Ancho de la imagen
                    img.height = 450  # Altura de la imagen

                    # Agregar la imagen a la hoja de trabajo
                    worksheet.add_image(img)
                    print("Marca de agua agregada correctamente.")

                except Exception as e:
                    print(f"Error al agregar la marca de agua: {e}")

                font = Font(b=True, color="FFFFFF")# color="00FFFFFF" -> blanco

                fill = PatternFill(fill_type="solid",
                        start_color='e84e0e',#'204D84',
                        end_color='e84e0e')#'204D84')
                

                
                fill_error = PatternFill(fill_type="solid",
                        start_color='a2ff00',#'204D84',
                        end_color='a2ff00')#'204D84')
                
                
                fill_vdf = PatternFill(fill_type="solid",
                        start_color='C8C8C8',#'204D84',
                        end_color='C8C8C8')#'204D84')
                

                    # Estilo de bordes: 
                border = Border(left=Side(border_style="thin", color="000000"),
                            right=Side(border_style="thin", color="000000"),
                            top=Side(border_style="thin", color="000000"),
                            bottom=Side(border_style="thin", color="000000"))


                #--  Diseño celdas  -- #


                Letra1 = get_column_letter(  h_offset + 1 ) # El  +1 es por desplazamiento lateral de las tablas
                Letra2 = get_column_letter(  h_offset + 2 )
                Letra3 = get_column_letter(  h_offset + 3 )


                cell_merge  = worksheet[Letra1 + str(v_offset +1)]
                cell_merge.font  = font  
                cell_merge.fill  = fill 


                # Definicion celdas
                cell_1 = worksheet[Letra1 + str(v_offset + 2)]  
                cell_2 = worksheet[Letra2 + str( v_offset +2 )]  
                cell_3 = worksheet[Letra3 + str( v_offset +2 )]                            
                # cell_4 = worksheet[Letra4 + str( v_offset +2 )]   
                    
                # Font:
                cell_1.font  = font  
                cell_2.font  = font  
                cell_3.font  = font 
                # cell_4.font  = font
                
                # Fill:
                cell_1.fill  = fill  
                cell_2.fill  = fill 
                cell_3.fill  = fill    
                # cell_4.fill  = fill    
                
                    

                #-- Combinar celdas de titulos --#
            
                worksheet.merge_cells(start_row=v_offset + 1, 
                                    start_column= h_offset +1, 
                                    end_row=v_offset +1, 
                                    end_column= h_offset +3
                                    ) # fila 2 ya que se deja una vacía al inicio
            

            # #-- Aplicar el borde a un conjunto de celdas --#
            
                cell_range = worksheet[Rangos[fecha_format]['celdaInicial']  : Rangos[fecha_format]['celdaFinal']   ]
                for row in cell_range:
                    for cell in row:
                        cell.border = border

                        # if row[0].value in colors:                        
                        #     cell.fill = PatternFill(fill_type="solid",  start_color=colors[row[0].value][:1] ,end_color= colors[row[0].value][1:] )
                

                        # elif row[0].value in rangos_vdf[channel]:
                        #     cell.fill = fill_vdf
                        


                #-- color texto --#
                for idx, row  in enumerate( worksheet.iter_rows(min_row=v_offset +3, max_row= max_column + v_offset +2)):
                    for cell in row:                                        
                        cell.font = font = Font(color=colors[fecha_format][idx][1:] )# color="00FFFFFF" -> blanco

                #-- alineacion de celdas --
                for idx, row  in enumerate( worksheet.iter_rows(min_row=v_offset +1, max_row= max_column + v_offset +2)):
                    for cell in row:
                        cell.alignment = Alignment(horizontal='center', 
                                                vertical='center', 
                                                shrink_to_fit=True,  
                                                wrap_text=False)
                        
                        # cell.font = font = Font(color=colors[fecha_format][idx][1:] )# color="00FFFFFF" -> blanco
                    
                # print(len( worksheet.iter_rows(min_row=v_offset, max_row= max_column + v_offset + 4)))

                        
                        
                        
                
                # Ajustar el ancho de las columnas solo para aquellas que contienen datos
                for column in worksheet.columns:
                    max_length = 0
                    for cell in column:
                        try:
                            if cell.value is not None and len(str(cell.value)) > max_length:
                                max_length = len(cell.value)
                        except:
                            pass
                    adjusted_width = (max_length + 2)
                    if adjusted_width > 2:  # Ajustar solo si hay datos en la columna
                        worksheet.column_dimensions[column[0].column_letter].width = adjusted_width


                        

        if 'Sheet' in workbook.sheetnames:
            workbook.remove(workbook['Sheet'])

        
        try:
            # Detectar la ruta del escritorio
            path_escritorio = get_escritorio_path()
            logger.info(f"Ruta detectada del escritorio: {path_escritorio}")


            identifier = f"{fechas[0]}_{fechas[-1]}" if fechas[0] != fechas[-1]  else f"{fechas[0]}"
            # Crear la ruta completa para el archivo Excel
            dest_path = os.path.join(path_escritorio, f"Reporte_historial_control_{identifier}.xlsx")

            # Verificar que la ruta exista
            if not os.path.exists(path_escritorio):
                raise FileNotFoundError(f"El escritorio no existe o no es accesible: {path_escritorio}")

            # Guardar el archivo Excel
            workbook.save(dest_path)
            if hasattr(workbook, 'close'):
                workbook.close()

            print(f"Archivo Excel guardado correctamente en: {dest_path}")
            return True

        except Exception as e:
            logger.error(f"Error al guardar el archivo: {e}")
            
                    
    except Exception as e:
        logger.error(f"Error generando reporte de historial de control: {e}  {e.__traceback__.tb_lineno}")




if __name__ == "__main__":

    rango_fechas = {"startDate": "10/04/2025", "endDate": "15/04/2025" }

    client = MongoClient("mongodb://10.0.0.53:27017")
    db_name = "NANOOX_TS"
    db = client[db_name]
    print(db.list_collection_names)

    dest_path = "C://Users//ITG//OneDrive - ITG Chile//Documentos//NANOOX2//ITG_NANOOX//NANOOX_BE//tests"

    # centro_id = "Loslagos-Puertomontt-Itg"
    centro_id = "Aysen-Natales-Natalino"
    # centro_id = "Aysen-Aysen-Quitralco"




    print(rango_fechas)
    _gen_data_report(db, rango_fechas=rango_fechas, excel_save_path= dest_path, file_name= f"Reporte_{centro_id}.xlsx" ,centro_id= centro_id)