from flask import Flask, jsonify
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity
from pymongo import MongoClient
from collections import defaultdict

user_database_name = "users"
enviro_database_name = "ENVIRO"
nanoox_database_name =  "NANOOX__TS" #"ACOPIO_NANOOX"
scapp_database_name = "ScappDB"

SERVICE_KEYS = ["enviro", "nanoox", "pss", "scapp"]

def get_db():
    """Obtiene una conexión de MongoDB por request."""
    if 'db_client' not in g:
        g.db_client = MongoClient("mongodb://localhost:27017/")
    return g.db_client


def intersect_service_status(ciclo_info: dict, user_status: bool, servicio: str) -> bool:
    """
    Intersecta el estado de permiso del usuario con el estado del ciclo para un servicio.
    """
    return bool(user_status and ciclo_info.get(servicio, {}).get('status', False))


def get_accesos_usuario(users_collection, username: str) -> dict:
    """
    Obtiene los permisos crudos (raw) del usuario desde la colección 'users'.
    """
    usuario = users_collection.find_one({'username': username})
    if not usuario:
        app.logger.warning(f"Usuario '{username}' no encontrado.")
        return {}
    return usuario.get('permisos', {})


def procesar_permisos(client, permisos_usuarios: dict) -> dict:
    """
    Cruza los permisos del usuario con los datos de la colección 'ciclos', retornando permisos activos.
    """
    ciclos = client[user_database_name].ciclos
    resultado = {}

    for region, areas in permisos_usuarios.get('permisos', {}).items():
        resultado[region] = {}
        for area, centros in areas.items():
            resultado[region][area] = {}
            for centro in centros:
                info = ciclos.find_one({'centro_id': centro.lower()})
                if not info:
                    # Sin datos de ciclo
                    resultado[region][area][centro] = []
                    continue

                nodo = {}
                for svc in SERVICE_KEYS:
                    user_status = permisos_usuarios['permisos'][region][area][centro] \
                        .get(svc, {}).get('status', False)
                    nodo[svc] = {'status': intersect_service_status(info, user_status, svc)}
                resultado[region][area][centro] = nodo
    return resultado


def transformar_diccionario(permisos_proc: dict) -> dict:
    """
    Transforma {R:{A:{C_ids: [...]}}} en {R:{A:{clave:{'_id':orig}}}}.
    clave = texto antes de '-' en cada ID.
    """
    out = {}
    for region, areas in permisos_proc.items():
        out[region] = {
            area: {
                centro_id.split('-', 1)[0]: {'_id': centro_id}
                for centro_id in centros
            }
            for area, centros in areas.items()
        }
    return out


def obtener_ids(data: dict) -> list:
    """
    Extrae lista de todos los valores '_id' en la estructura transformada.
    """
    ids = []
    for areas in data.values():
        for centros in areas.values():
            for nodo in centros.values():
                _id = nodo.get('_id')
                if _id:
                    ids.append(_id)
    return ids


def status_servicio_permiso_usuario(client, username: str, centro_id: str, servicio: str) -> bool:
    """
    Consulta la colección 'users' con pipeline para extraer el status de un servicio.
    """
    users = client[user_database_name].users
    pipeline = [
        {'$match': {'username': username}},
        {'$project': {'permisos': {'$objectToArray': '$permisos'}}},
        {'$unwind': '$permisos'},
        {'$project': {'ciudades': {'$objectToArray': '$permisos.v'}}},
        {'$unwind': '$ciudades'},
        {'$project': {'centros': {'$objectToArray': '$ciudades.v'}}},
        {'$unwind': '$centros'},
        {'$project': {'centro_id': '$centros.k', 'servicios': {'$objectToArray': '$centros.v'}}},
        {'$unwind': '$servicios'},
        {'$match': {'centro_id': centro_id, 'servicios.k': servicio}},
        {'$project': {'_id': 0, 'status': '$servicios.v.status'}}
    ]
    res = list(users.aggregate(pipeline))
    return res[0]['status'] if res else False


def obtener_estado_servicio_centro(client, centro_id: str):
    """
    Retorna el estado de 'enviro' y 'nanoox' desde la colección 'estados'.
    """
    col = client[user_database_name].estados
    doc = col.find_one({'centro_id': centro_id}, {'_id': 0})
    return doc if doc else {'enviro': 'B', 'nanoox': 'B'}


def estado_centro(estado_enviro, estado_nanoox) -> str:
    """
    Obtiene el estado resultante del centro según el estado de Enviro y Nanoox ('C' > 'A' > 'B').
    """
    estados = [estado_enviro, estado_nanoox]
    if any(e == 'C' for e in estados):
        return 'C'
    if any(e == 'A' for e in estados):
        return 'A'
    return 'B'


def agrupar_por_region_area(data: dict) -> dict:
    """
    Agrupa centros bajo clave 'region-area' con valor None.
    """
    out = defaultdict(dict)
    for region, areas in data.items():
        for area, centros in areas.items():
            key = f"{region}-{area}"
            for nodo in centros.values():
                out[key][nodo['_id']] = None
    return dict(out)


def obtener_coordenadas(client: MongoClient, centro_id: str):
    """
    Retorna {'lat': ..., 'lon': ...} desde db users coleccion ciclos
    inspeccionando gps_coordinates.active ('manual' o 'auto').
    """
    ciclos = client[user_database_name].ciclos
    # Solo traemos gps_coordinates para no cargar todo el doc
    doc = ciclos.find_one(
        {'centro_id': centro_id.lower()},
        {'_id': 0, 'gps_coordinates': 1}
    )

    if not doc or 'gps_coordinates' not in doc:
        return {'lat': None, 'lon': None}

    gps = doc['gps_coordinates']
    activo = gps.get('active')
    # Obtenemos el subdocumento indicado por 'active'
    sub = gps.get(activo, {})
    return {
        'lat': sub.get('lat'),
        'lon': sub.get('lon')
    }

# def obtener_coordenadas(client: MongoClient, centro_id: str) -> dict:
#     """
#     Retorna {'lat': ..., 'lon': ...} desde db users coleccion ciclos campo georef.
#     """
#     ciclos = client[user_database_name].ciclos
#     doc = ciclos.find_one({'centro_id': centro_id.lower()}, {'_id': 0, 'georef.lat': 1, 'georef.lon': 1})
#     if doc and 'georef' in doc:
#         return {'lat': doc['georef'].get('lat'), 'lon': doc['georef'].get('lon')}
#     return {'lat': None, 'lon': None}

# def clean_none(data):
#     """
#     ORIGINAL. Elimina entradas None o vacías en dicts y listas.
#     """
#     if isinstance(data, dict):
#         cleaned = {}
#         for k, v in data.items():
#             sub = clean_none(v)
#             if sub is not None:
#                 cleaned[k] = sub
#         return cleaned or None
#     if isinstance(data, list):
#         lst = [clean_none(i) for i in data]
#         return [i for i in lst if i is not None] or None
#     return data
def clean_none(data):
    """
    Elimina recursivamente claves con valor None o listas vacías.
    Devuelve None si el objeto completo queda vacío.
    """
    if isinstance(data, dict):
        to_del = []
        for k, v in data.items():
            cv = clean_none(v)
            if cv is None:
                to_del.append(k)
            else:
                data[k] = cv
        for k in to_del:
            del data[k]
        return data or None

    if isinstance(data, list):
        cleaned = [clean_none(x) for x in data]
        cleaned = [x for x in cleaned if x is not None]
        return cleaned or None

    return data

def actualizar_valor(valor):
    """
    A partir de una lista de estados retorna el valor predominante(C>A>B).
    Si existe al menos una C, retornará C; de existir al menos una A sin
    existencia de C, retornará A. De no existir, retornará B
    """
    if 'C' in valor:
        return 'C'
    elif 'A' in valor:
        return 'A'
    else:
        return 'B'

# def estado_resultante_area_region(consolidado: dict) -> dict:
#     """
#     ORIGINAL. Concatena y actualiza estados por 'region-area'.
#     """
#     out = {}
#     for region_area, centros in consolidado.items():
#         concat = ''.join(v if v is not None else 'B' for v in centros.values())
#         out[region_area] = actualizar_valor(concat)
#     return out
# def estado_resultante_area_region(consolidado_por_centro: dict) -> dict:
#     """
#     SEGUNDO:funcionó a medias. Concatena y colapsa por 'R-A', luego aplica actualizar_valor.
#     Ahora extrae el campo 'estado' si el valor es un dict con 'estado'.
#     """
#     out = {}
#     for ra, centros in consolidado_por_centro.items():
#         # Para cada centro, extraer el estado (str) del diccionario o usar 'B' por defecto
#         estados = []
#         for item in centros.values():
#             if isinstance(item, dict) and 'estado' in item:
#                 estados.append(item['estado'])
#             elif isinstance(item, str):
#                 estados.append(item)
#             else:
#                 estados.append('B')
#         concatenated = ''.join(estados)
#         out[ra] = actualizar_valor(concatenated)
def estado_resultante_area_region(consolidado_por_centro: dict) -> dict:
    """
    Concatena y colapsa por 'R-A', luego aplica actualizar_valor.
    Extrae el campo 'estado' cuando el valor es un dict.
    """
    out = {}
    for ra, centros in (consolidado_por_centro or {}).items():
        # Extraer lista de estados en orden
        estados = []
        for item in centros.values():
            if isinstance(item, dict) and 'estado' in item:
                estados.append(item['estado'])
            elif isinstance(item, str):
                estados.append(item)
            else:
                estados.append('B')
        concatenated = ''.join(estados)
        out[ra] = actualizar_valor(concatenated)
    return out


def estado_resultante_region(area_region_states: dict) -> dict:
    """
    Agrupa por región (parte antes del guión), concatena los valores resultantes
    de estado por área y aplica actualizar_valor.
    """
    agg = defaultdict(str)
    for ra, val in (area_region_states or {}).items():
        # val ya es un string 'B', 'A' o 'C'
        region = ra.split('-', 1)[0]
        agg[region] += val or 'B'
    return {r: actualizar_valor(v) for r, v in agg.items()}


# def estado_resultante_region(est_area: dict) -> dict:
#     """
#     ORIGINAL.Agrupa y actualiza estados por región.
#     """
#     temp = defaultdict(str)
#     for ra, val in est_area.items():
#         region, _ = ra.split('-')
#         temp[region] += val
#     return {r: actualizar_valor(v) for r, v in temp.items()}


def fetch_effective_permissions(client, users_coll, username: str, page: str) -> dict:
    """
    Retorna permisos de usuario procesados según PAGE: raw o cruzados con ciclos.
    """
    raw = get_accesos_usuario(users_coll, username)
    if page.upper() == 'HOME':
        return raw
    return procesar_permisos(client, {'permisos': raw})


def compute_new_values(client, page: str, data: dict, username: str) -> dict:
    """
    Calcula estado actual de cada centro según PAGE y permisos.
    """
    ciclos = client[user_database_name].ciclos
    nuevos = {}
    for centro_id in obtener_ids(data):
        permitido = (page.upper() == 'HOME') or \
            status_servicio_permiso_usuario(client, username, centro_id, page.lower())
        if not permitido:
            continue
        state_env = None
        state_nan = None
        if page.upper() in ('HOME', 'ENVIRO'):
            state_env = obtener_estado_servicio_centro(client, centro_id)['enviro']
        if page.upper() in ('HOME', 'NANOOX'):
            state_nan = obtener_estado_servicio_centro(client, centro_id)['nanoox']
        nuevos[centro_id] = estado_centro(state_env, state_nan)
    return nuevos

#ORIGINAL SIN COORDENADAS
# def agregar_estado_centros_a_consolidado(grupo: dict, dict_estados: dict) -> dict:
    """
    Inserta estado de cada centro en la agrupación.
    """
#     for centro_id, state in dict_estados.items():
#         for region_area, centros in grupo.items():
#             if centro_id in centros:
#                 centros[centro_id] = state
#     return grupo

def agregar_estado_centros_a_consolidado(grupo: dict, dict_estados: dict, client: MongoClient) -> dict:
    """
    Inserta estado y coordenadas de cada centro en el diccionario de permisos.
    """
    for ra, centros in grupo.items():
        for centro_id in list(centros):
            if centro_id in dict_estados:
                centros[centro_id] = {
                    'estado': dict_estados[centro_id],
                    'coordenadas': obtener_coordenadas(client, centro_id)
                }
    return grupo

#ORIGINAL SIN COORDENADAS
# def consolidado_empresa(client, username: str, page: str) -> dict:
#     """
#     Flujo completo de consolidado: permisos → transformar → valores → merge → jerarquías.
#     """
#     users_coll = client[user_database_name].users
#     permisos = fetch_effective_permissions(client, users_coll, username, page)
#     data = transformar_diccionario(permisos)
#     nuevos = compute_new_values(client, page, data, username)
#     centros = clean_none(agregar_estado_centros_a_consolidado(
#         agrupar_por_region_area(data), nuevos
#     ))
#     areas = estado_resultante_area_region(centros)
#     regiones = estado_resultante_region(areas)
#     return {'centros': centros, 'areas': areas, 'regiones': regiones}

def consolidado_empresa(client: MongoClient, username: str, page: str):
    """
    Orquesta todo el flujo del consolidado:
      1. Permisos → 2. Transformar → 3. Valores → 4. Merge+coords →
      5. Jerarquías (áreas y regiones)
    """
    try:
        print("EN CONSOLIDADO EMPRESAS")
        users_coll = client[user_database_name].users
        permisos = fetch_effective_permissions(client, users_coll, username, page)
        # print(f"{permisos=} \n")
        data = transformar_diccionario(permisos)
        # print(f"{data=} \n")
        nuevos = compute_new_values(client, page, data, username)
        # print(f"{nuevos=} \n")
        centros = clean_none(
            agregar_estado_centros_a_consolidado(
                agrupar_por_region_area(data), nuevos, client
            )
        )
        # print(f"{centros=} \n")
        areas = estado_resultante_area_region(centros or {})
        # print(f"{areas=} \n")
        regiones = estado_resultante_region(areas or {})
        # print(f"{regiones=} \n")
        return {'centros': centros, 'areas': areas, 'regiones': regiones}
    except Exception as e:
        print(f"Error obteniendo consolidado empresa en línea {e.__traceback__.tb_lineno}: \n{e}")
        return None