##
# @file TransformationFunctions.py
#
# @brief Funciones para la transformacion de la colección original al nuevo formato.
# 
# @authors DopamineLabs LTDA
# @date 2025
##

from .DataDefinitions import *
from datetime import datetime
from tqdm import tqdm

def check_position_values(value):
    """! Realiza la verificación del tipo de dato resultante para la estructura de posicion del documento transformado. 
    @param value Valor de la estructura de validación que se busca verificar. 
    @return El valor validado. En el caso de que la validación no pueda ser realizada, retorna el valor por defecto. 
    """
    # Check if string
    if type(value) is str:
        # Check if default value is correct
        if value != "-":
            return "-"
        else:
            return value
    # Check if integer
    elif type(value) is int:
        return value
    
    else:
        return "-"

def check_key_against_data(key, data, default_value, transformator=None):
    """! Revisa si el valor pasado como llave existe en el diccionario, en caso contrario, retorna valor por defecto.
    @param key Llave para obtener el valor desde <data>
    @param data La estructura que se busca verificar.
    @param Valor por defecto para <data[key]> en caso de no estar presente. 
    @param transformator Permite realizar la transformación del valor a ser retornado, mediante una función lambda. 
    @return Retorna el valor validado frente al <default_value>
    """
    data_keys = data.keys()
    # Assign default value and then check
    value = default_value
    if key in data_keys:
        if(data[key] != None):
            value = data[key]
    
    if transformator:
        value = transformator(value)

    return value

def convert_data_iso(date, formater='%Y-%m-%d %H:%M:%S'):
    """! Realiza la conversión de un string con representación de fecha-hora, en su representación de objeto fecha.
    @param date El string a convertir
    @param formater El string que contiene el formato de fecha-hora a utilizar en la conversión. 
    @return El objeto fecha (datetime) resultante.
    """
    timestamp_obj = datetime.strptime(date, formater)
    return timestamp_obj

def parse_metadata(day, cage):
    """! Retorna un diccionario con el formato de metadata para la colección transformada.
    @param day String con la representación de la fecha en formato YYYY-mm-dd.
    @param cage String con el nombre de la jaula. 
    @return Retorna diccionario con la metadata.

    """
    return {'Dia':day, 'Jaula': cage}

def parse_detecciones(data):
    """! Convierte la información de la jaula proveniente del documento de la colección original, al formato de la estructura
    de 'Detecciones' en la colección transformada. 
    @param data Información de la entrada asociada a la jaula, proveniente de la colección original. 
    @return Retorna un diccionario con la estructura esperada para 'Detecciones' en la colección transformada. 
    """
    # Always present fields
    return_dict = {
        'perdidos': data['perdidos'],
        'detectados': data['detecciones'],
        'nivel_actividad': data['nivel_actividad'],
        # Añadidos post conversación con Esteban 12/03/25
        'umbral_alarma' : 3,    # HARDCODEO DEL DURO
    }

    # Check if perdidos is present
    perdidos_propios_value = check_key_against_data('perdidos_propios', data, 0)
    return_dict['perdidos_propios'] = perdidos_propios_value

    return return_dict

def parse_alimentacion(data):
    """! Convierte la información de la jaula proveniente del documento de la colección original, al formato de la estructura
    de 'Alimentación' en la colección transformada. 
    @param data Información de la entrada asociada a la jaula, proveniente de la colección original. 
    @return Retorna un diccionario con la estructura esperada para 'Alimentación' en la colección transformada. 
    """
    # Always present fields
    return_dict = {
        'use_feeding_api': data.get('use_feeding_api',False),
        'detection_active': check_key_against_data('detection_active', data, NEW_ALIMENTACION_STRUCTURE['detection_active'], lambda b: bool(b))
    }
    # Check comida entregada key
    comida_entregada_value = check_key_against_data('Comida_entregada', data, 0)   
    # Check meta dia key
    meta_dia_value = check_key_against_data('Meta_dia', data, 0)
    # Check porc entregado key
    porc_entregado_value = check_key_against_data('Porc_entregado', data, 0, lambda t: int(t))
    # Check tasa key 
    tasa_value = check_key_against_data('Tasa', data, 0)
    # Check dia key
    dia_value = check_key_against_data('Dia', data, 0, lambda t: float(t))
    # Check feeding_active key
    feeding_active_value = check_key_against_data('Feeding_active', data, False)
    # Check unidad medida key
    unidad_medida_key = check_key_against_data('Unidad_Medida', data, "kg/min")
    # Check Metodo_alimentacion
    metodo_alimentacion_key = check_key_against_data("Metodo_alimentacion", data, "Saciedad")
    # Checkeo feeding_api_type
    feeding_api_type = check_key_against_data("feeding_api_type", data, "AkvaControl")
    # Add checked values
    return_dict['Comida_entregada'] = comida_entregada_value
    return_dict['Meta_dia'] = meta_dia_value
    return_dict['Porc_entregado'] = porc_entregado_value
    return_dict['Tasa'] = tasa_value
    return_dict['Dia'] = dia_value
    return_dict['Feeding_active'] = feeding_active_value
    return_dict['Unidad_Medida'] = unidad_medida_key
    # Añadidos post conversación con Esteban
    # return_dict['Metodo_alimentacion'] = metodo_alimentacion_key
    return_dict['feeding_api_type'] = feeding_api_type

    return return_dict

def rectify_visita(visit):
    """! Función que convierte las entradas de visitas, del formato asociado a la colección original 
    al formato de la colección transformada. Principalmente realiza la transformación de las fechas a 
    ISODATE. 
    @param visit Entrada de visita a convertir al formato de la colección transformada. 
    @return Retorna la entrada transformada. 
    """
    new_visit = {
        "duracion": visit["duracion"],
        "racion": visit["racion"],
        "inicio": convert_data_iso(visit['inicio']),
        "final": convert_data_iso(visit['final']),
        'metodo_alimentacion': 'Saciedad',      # No estaba implementado FeedStation aún
        # "sfr" : 0,                              # No estaba implementado FeedStation aún
        # "cantidad_peces" : 0,                   # No estaba implementado FeedStation aún
        # "peso_promedio" : 0,                    # No estaba implementado FeedStation aún
    }
    return new_visit

def parse_visitas(data):
    """! Convierte la información de la jaula proveniente del documento de la colección original, al formato de la estructura
    de 'Visitas' en la colección transformada. 
    @param data Información de la entrada asociada a la jaula, proveniente de la colección original. 
    @return Retorna un diccionario con la estructura esperada para 'Visitas' en la colección transformada. 
    """
    visitas = check_key_against_data('Visitas', data, OLD_CAGE_STRUCTURE['Visitas']['default'])
    return_list = []
    if len(visitas) == 0 or visitas is None: 
        return []
    # If not empty parse each visit
    for visit_key in visitas:
        rect_visit = rectify_visita(visitas[visit_key])
        # Check if 'duracion' is valid and not None
        if(rect_visit['duracion'] != None):
            return_list.append(rect_visit)
    return return_list


def parse_posicion(data):
    """! Convierte la información de la jaula proveniente del documento de la colección original, al formato de la estructura
    de 'Posicion' en la colección transformada. 
    @param data Información de la entrada asociada a la jaula, proveniente de la colección original. 
    @return Retorna un diccionario con la estructura esperada para 'Posicion' en la colección transformada. 
    """
    # Unpack values
    x_pos_value = check_key_against_data('x_pos', data, "-", check_position_values)
    y_pos_value = check_key_against_data('y_pos', data, "-", check_position_values)
    winche_value = check_key_against_data('winche_connected', data, False)
    
    return {
        'x_pos': x_pos_value,
        'y_pos': y_pos_value,
        'winche_connected': winche_value
    }


def document_transformer(doc, verbose=True, progress_bar=None):
    """! Funcion que tomar un documento de la colección original y lo transforma en documentos de la colección transformada (salida). 
    @param doc Documento proveniente de la colección original
    @param verbose Indica si se muestran los prints de procesamiento.
    @param progress_bar Referencia a la barra de progreso utilizada. 
    @return Retorna un listado con las ids de los documentos generados en la colección transformadas, asociados al documento de entrada 
    de la colección original.
    """
    # Each document must be subdivided onto a list of new docs
    new_docs = []
    
    # * Re estructuración de visitas
    new_visits_docs = {}

    # Get doc identifier data
    # -> This data must be present on entry
    entry_id = doc['_id']
    entry_day = doc['dia']
        
    # -> Data that could be missing from entry
    # entry_metodo_alimentacion = check_key_against_data('Metodo_alimentacion', doc, OLD_TOP_LEVEL_STRUCTURE['Metodo_alimentacion']['default'])
    
    # -> Analyze cage data
    for cage_entry in doc:
        # Filter only cage information
        cage_data = doc[cage_entry]
        if(type(cage_data) != list):
            continue
        else:
            # Iterate through all cage entries
            for cage_update in cage_data:
                timestamp = convert_data_iso(cage_update['hora'])
                # Parse metadata dictionary
                # metadata = parse_metadata(entry_day, cage_entry)
                # Parse detecciones dictionary
                detecciones = parse_detecciones(cage_update)
                # Parse alimentacion dictionary
                alimentacion = parse_alimentacion(cage_update)
                # Parse visitas dictionary
                visitas = parse_visitas(cage_update)
                # Parse posiciones dictionary
                posiciones = parse_posicion(cage_update)
                # Parse sensores dictionary
                sensores = {'temp': check_key_against_data('temp', cage_update, '-')}

                # Temp 
                if(visitas == None):
                    print(f"None error found  _id : {entry_id}")

                # Parse transformed entry
                transformed_entry = {
                        "timestamp": timestamp,
                        "camera_id": cage_entry,  # obsoleto -> "metadata": metadata,
                        "Detecciones": detecciones,
                        "Alimentacion": alimentacion,
                        # "Visitas": visitas,
                        "Posicion": posiciones, 
                        "Sensores": sensores,
                        # "Metodo_alimentacion": entry_metodo_alimentacion   # Quitado luego de conversación con Esteban
                    }
                new_docs.append(transformed_entry)
                
                # * Re estructuración de visitas
                if visitas:
                    transformed_visits = {
                        "camera_id": cage_entry,    # obsoleto -> # "metadata": metadata,
                        "day": entry_day,
                        "Visitas": visitas,
                    }
                    new_visits_docs[cage_entry] = transformed_visits
                
    # Inform the _id of the origin document and the number of resulting documents
    if progress_bar and verbose:
        progress_bar.write(f'[INFO] Document with _id: {entry_id} was transformed onto {len(new_docs)} documents')
    return new_docs, new_visits_docs

        
        
        