# ---- Importaciones de Librerias---- #
""" from app import app, socketIo """
import copy
import os
import re

# from flask_cors import CORS
from datetime import datetime, timedelta
# from flask import Flask, jsonify, request, send_from_directory, send_file, after_this_request, abort
# from flask_jwt_extended import (JWTManager, create_access_token, create_refresh_token,
#                                 get_jwt_identity, jwt_required)
# from werkzeug.security import check_password_hash, generate_password_hash                              
from pymongo import MongoClient, UpdateOne, InsertOne
from pymongo.errors import BulkWriteError

from .ambiental.generate_server_report import generate_report_from_server

import numpy as np
from pprint import pprint 

# ---- Conexion a bases de datos de usuarios, ciclos y servicios---- #
#Conexion a Servidor local de MongoDB
client = MongoClient('mongodb://localhost/', 27017)

# Definicion de nombres de bases de datos globales de un solo cliente
user_database_name = "users"
enviro_database_name = "ENVIRO"

# Definicion de DB de usuarios/ciclos y servicios  
user_db = client[user_database_name]
enviro_db = client[enviro_database_name]

# Definicion de colecciones de usuarios,ciclos y servicios (datos y metadatos)
#Acceso a coleccion usuarios
users_collection    = user_db.users

#Acceso a colección ciclos
ciclos_collection = user_db.ciclos

#Acceso a colección data Enviro
enviro_data_collection = enviro_db.data
#Acceso a colección metadata Enviro
enviro_metadata_collection = enviro_db.metadata

# ------Definicion de funciones Auxiliares------ #

#!---FUNCIONES AMBIENTAL---!

# Obtener la informacion de todas las estaciones
#i!
def _get_all_station_data( document, all_day_data ):
    """ Procesa los datos de las estaciones de un documento y devuelve un diccionario con los datos solicitados.

    :param document: El documento de MongoDB que contiene los datos de las estaciones.
    :param all_day_data: Booleano que indica si se deben devolver todos los datos del día (True) o solo el último dato (False).
    :return: Un diccionario con los datos de las estaciones procesados.
    """
    all_station_data = {}
    if document:
        for estacion in document.get('estaciones', []):
            station_name = estacion['Nombre']
            station_data = estacion['datos']
            
            if all_day_data:
                all_station_data[station_name] = station_data
            else:
                # Asumiendo que los datos están ordenados cronológicamente
                all_station_data[station_name] = station_data[-1] if station_data else None
    
    return all_station_data
#i!
def _save_client_data( collection, all_sensors_data, centroId ):
    # Lista para almacenar las operaciones de actualización
    bulk_operations = []
    print(f'_save_client_data: {all_sensors_data}')
    try:

        # Iterar sobre cada estación en all_sensors_data
        for estacion in all_sensors_data:
            #print(f'estacion: {estacion}\n')
            #print(f'estacion[fecha]: {estacion['fecha']}\n')
            # Obtener la fecha y el nombre de la estación
            full_date = estacion['fecha']
            date = full_date.split(' ')[0]
            formatted_date = f"{date.split('-')[2]}-{date.split('-')[1]}-{date.split('-')[0]}"  # Cliente envía fecha: dia-mes-año
            station_name = estacion['Nombre']

            if collection.find_one({'estaciones.datos.Nombre':station_name, "estaciones.datos.fecha": full_date}) == None:
                print("No hay data que coincida")
                # Crear una operación de actualización para cada conjunto de datos de la estación
                update_operation = UpdateOne(
                {   # Condición de búsqueda
                    'centroId': centroId,
                    'fecha': formatted_date,
                    'estaciones.Nombre': station_name,
                },  
                {
                    '$push': {'estaciones.$.datos': estacion}
                },
                upsert=True  # Insertar el documento si no existe
                )

                # Agregar la operación a la lista
                bulk_operations.append(update_operation)
                print(f'Dato de centro {centroId} añadido con fecha {formatted_date}')
            # Ejecutar todas las operaciones de actualización en una sola llamada a la base de datos
            else:
                print("Dato ya existe")

        if bulk_operations:
            collection.bulk_write(bulk_operations)
            print(f'Datos guardados con bulk_write en {collection}')

    # Capturamos el error específico que ocurre durante la operación bulk_write
    except BulkWriteError as e:
        print(f'Detectado BulkWrite Error, intentando solucionar...')
        # Verificamos si el error se debe a que el operador posicional no encontró una coincidencia
        # Este mensaje específico indica que se intentó actualizar una estación que no existe en el documento
        if 'The positional operator did not find the match needed from the query' in str(e):
            # Si este es el caso, necesitamos asegurarnos de que el documento y la estación específica existan

            for estacion in all_sensors_data:
                # Obtenemos la fecha y el nombre de la estación como antes
                full_date = estacion['fecha']
                date = full_date.split(' ')[0]
                formatted_date = f"{date.split('-')[2]}-{date.split('-')[1]}-{date.split('-')[0]}"
                station_name = estacion['Nombre']

                # Paso 1: Asegurar la existencia del documento base
                # Si el documento con centroId y fecha no existe, se crea con un arreglo de estaciones vacío
                collection.update_one(
                    {'centroId': centroId, 'fecha': formatted_date},
                    {'$setOnInsert': {'estaciones': []}},
                    upsert=True
                )

                # Paso 2: Asegurar que la estación específica exista en el arreglo
                # Si no existe una estación con el nombre dado, se agrega al arreglo
                collection.update_one(
                    {'centroId': centroId, 'fecha': formatted_date, 'estaciones.Nombre': {'$ne': station_name}},
                    {'$push': {'estaciones': {'Nombre': station_name, 'datos': []}}}
                )

                # Paso 3: Actualizar los datos de la estación específica
                # Una vez asegurado que el documento y la estación existen, actualizamos los datos
                collection.update_one(
                    {'centroId': centroId, 'fecha': formatted_date, 'estaciones.Nombre': station_name},
                    {'$push': {'estaciones.$.datos': estacion}}
                )

                print(f'Dato de centro {centroId} corregido y añadido con fecha {formatted_date}')

        # Si el error no es debido a un problema con el operador posicional, se imprime el error
        else:
            print(f'Error guardando datos del centro {centroId}: \n{e}')
        
    except Exception as e:
        print(f'Error guardando datos del centro {centroId} en linea {e.__traceback__.tb_lineno}: \n{e}')
    
    return
#i!
def check_metadata(REGION, AREA, CENTRO):
    """ Comprueba y actualiza la metadata existente o crea una nueva. """
    # Obtener toda la metadata existente
    existing_metadata = enviro_metadata_collection.find_one({})

    # Si no hay metadatos, crear nuevos
    if not existing_metadata:
        new_metadata = _make_new_metadata(REGION, AREA, CENTRO)
        enviro_metadata_collection.insert_one(new_metadata)
        print(f'Metadata creada {CENTRO}-{AREA}-{REGION}')
        return

    # Crear una copia profunda para realizar modificaciones
    modified_metadata = copy.deepcopy(existing_metadata)

    # Navegar o crear cada nivel
    modified_metadata = _update_or_create_metadata_anidation(modified_metadata, REGION, AREA, CENTRO )

    # Actualizar metadata en la coleccion
    if modified_metadata != existing_metadata:
        enviro_metadata_collection.update_one({}, {"$set": modified_metadata})
        print(f'Metadata modificada con datos de {CENTRO}-{AREA}-{REGION}')

    return

def _update_or_create_metadata_anidation(metadata, REGION, AREA, CENTRO):
    """ Navega o crea secciones en la metadata basado en las claves proporcionadas. """
    if REGION not in metadata:
        metadata[REGION] = {}
        
    if AREA not in metadata[REGION]:
        metadata[REGION][AREA] = {}

    if CENTRO not in metadata[REGION][AREA]:
        metadata[REGION][AREA][CENTRO] = { '_id': f'{CENTRO}-{AREA}-{REGION}' }
    
    return metadata

def _make_new_metadata(REGION, AREA, CENTRO):
    """ Crea una nueva estructura de metadata. """
    return {
            REGION: {
                AREA: {
                    CENTRO: {
                        '_id': f'{CENTRO}-{AREA}-{REGION}'
                    }
                }
            }
        }
