import base64
from datetime import datetime
from enum import Enum
import json
import requests
from functools import wraps
import re
from flask import Flask
from functools import wraps

import os
import grpc

from kubernetes import client, config

from flask_cors import CORS
import sys

app = Flask(__name__)
cors = CORS(app, resources={r"/*": {"origins": "*"}})

PATH_APP = os.path.dirname(__file__)

path_app_main = os.path.join(PATH_APP, "App/main")
sys.path.append(path_app_main)
path_app_protos = os.path.join(PATH_APP, "App/protos")
sys.path.append(path_app_protos)

from werkzeug.security import check_password_hash

from flask import jsonify, request, make_response
import jwt
from google.protobuf.json_format import MessageToJson

from csharp_dao import cloudServiceDAO_pb2, cloudServiceDAO_pb2_grpc
from python_carbonblackmanager import CBCManager_pb2, CBCManager_pb2_grpc



# Host de microservicio cloudServiceDAO (Intermediario para transacciones a base de datos)
DAO_HOST = os.environ["DAO_HOST"]
DAO_PORT = os.environ["DAO_PORT"]
# Host de microservicio CBCManager
CBC_HOST = os.environ["CBC_HOST"]
CBC_PORT = os.environ["CBC_PORT"]
# Secretos
SECRET_NAMESPACE = os.environ["SECRET_NS"]



# app = Flask(__name__)

# config.load_incluster_config()

# v1 = client.CoreV1Api()

# try:
#     # Secreto para autenticación requerida para cualquier consulta API (se requiere la existencia previa de secreto dentro de cluster)
#     secret_api = str(
#         v1.read_namespaced_secret(name="secret-key", namespace=SECRET_NAMESPACE).data[
#             "key"
#         ]
#     )
#     # Secreto para autenticación requerida desde consultas de terraform provider (se requiere la existencia previa de secreto dentro de cluster)
#     secret_tf = str(
#         v1.read_namespaced_secret(
#             name="secret-tf-key", namespace=SECRET_NAMESPACE
#         ).data["key"]
#     )
#     vcd_service_account = str(
#         v1.read_namespaced_secret(name="vcd-sa", namespace=SECRET_NAMESPACE).data["key"]
#     )
# except Exception as e:
#     print(f"Error al recuperar secretos desde namespace {SECRET_NAMESPACE}: {e}")
#     exit(1)

# app.config["TF_SECRET_KEY"] = base64.b64decode(secret_tf)
# app.config["SECRET_KEY"] = base64.b64decode(secret_api)

vcd_service_account = "ZGF2aWQuZXN0cmFkYUBTR01fU1RHOkEjZWw5NjFNMlZZMmZnazU="

class STATUS(Enum):
    # Listado de estatus utilizados en tabla RequestQueue (aprovisionamiento y servicio)
    REQUESTED = 1
    SUCCESS = 2
    SUPPLY_ERROR = 3
    ACTIVE = 4
    INACTIVE = 5
    SERVICE_ERROR = 6
    DELETED = 7


def token_required(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        token = None
        vcd_token = "authorization" in request.headers and "org_id" in request.args
        tf_token = "tf-daas-token" in request.headers

        if "x-daas-token" in request.headers:
            # Token generado por autenticación básica método API /session
            token = request.headers["x-daas-token"]
        elif tf_token:
            # Token generado desde método API /session/terraform
            token = request.headers["tf-daas-token"]
        elif vcd_token:
            # Bearer token generado desde API de VCD o recuperado desde plugin
            token = request.headers["authorization"]
        else:
            return jsonify(
                {
                    "status": "ERROR",
                    "message": "No se encuentran datos de autenticación",
                }
            )

        with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
            ca_cert = fp.read()

        creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
        stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
            grpc.secure_channel(f"{DAO_HOST}:8445", credentials=creds)
        )

        if tf_token or not vcd_token:
            try:
                # Decodificación de token
                data = jwt.decode(
                    token,
                    app.config["TF_SECRET_KEY" if tf_token else "SECRET_KEY"],
                    algorithms=["HS256"],
                )
                # Validación de id de usuario extraído desde token vs registro en BD
                req = cloudServiceDAO_pb2.GetUserVCDRequest(public_id=data["public_id"])
                user_found = stub.getUserVCD(req)

                if not user_found:
                    return jsonify(
                        {
                            "status": "ERROR",
                            "message": "No se autenticó correctamente o el token es inválido",
                        }
                    )
            except Exception as e:
                return jsonify({"status": "ERROR", "message": f"token inválido: {e}"})
        else:
            req = cloudServiceDAO_pb2.GetOrgVCDRequest(
                org_id=int(request.args["org_id"])
            )
            org = stub.getOrgVCD(req)

            if not org.orgs:
                return jsonify(
                    {
                        "status": "ERROR",
                        "message": "No se encuentra registrada la organización indicada",
                    }
                )

            if len(org.orgs) > 1:
                # El nombre de las organizaciones registrdados en BD deben ser únicos
                return jsonify(
                    {
                        "message": f"Se encontraron múltiples organizaciones con el identificador entregado al intentar validar usuario, ésto no debería de ser posible e impide reconocer el usuario al que desea hacer referencia. Reporte éste error"
                    }
                )

            org = org.orgs[0]
            # Versión de API soportada por versión de VCD a consumir
            api_version = org.api_version
            api_versions = []

            if api_version is None or api_version.strip() == "":
                # En caso de no existir la versión de API en BD se probará con una versión válida
                try:
                    res = requests.get(
                        f"{org.host}/cloudapi/1.0.0/orgs",
                        timeout=120,
                        headers={
                            "Authorization": token,
                        },
                    )  # Al no contar con el header accept en el formato requerido según la documentación de la API, se espera que la respuesta nos arroje un listado de versiones de API válidas hasta la fecha

                    if res.status_code != 406:
                        res.raise_for_status()
                    if (
                        "minorErrorCode" in res.json()
                        and res.json()["minorErrorCode"] == "NOT_ACCEPTABLE"
                    ):
                        r = re.search(r"(?<=\[)(.*)(?=\])", res.json()["message"])
                        api_versions = r.group(1).split(",")
                        api_versions = [
                            x.strip() for x in api_versions if "D" not in x
                        ]  # D es para versiones obsoletas (Deprecated). Éstas versiones son descartadas
                    else:
                        return jsonify(
                            {
                                "message": "La validación de token falló, reporte éste error con su proveedor del servicio"
                            }
                        )  # Es posible que ya no se entreguen las versiones de API desde una petición incompleta en un futuro (sin header Accept requerida), ésto requerirá ajustar petición o la validación
                except:
                    return jsonify(
                        {"message": "No fue posible validar versión de api VCD"}
                    )
            else:
                api_versions.append(api_version)

            for av in api_versions:
                try:
                    res = requests.get(
                        f"{org.host}/cloudapi/1.0.0/orgs",
                        timeout=120,
                        headers={
                            "Accept": f"application/*;version={av}",
                            "Authorization": token,
                        },
                    )

                    if res.status_code == 406:
                        continue

                    res.raise_for_status()

                    if not "resultTotal" in res.json() or res.json()["resultTotal"] < 1:
                        return jsonify(
                            {
                                "message": "No fue posible validar vigencia de token, reporte éste error con su proveedor del servicio"
                            }
                        )  # La respuesta a la petición cambió, hay que cambiar a otra petición simple de la api vcd (cualquiera que no requiera muchos parámetros)
                    else:
                        # No existe método update para tabla org_vcd
                        # Es posible actualizar versión de API en base de datos
                        # current_user = tenant
                        break
                except:
                    return jsonify({"message": "No se autenticó"})
        return f(token, *args, **kwargs)

    return decorator


@app.route("/session/terraform", methods=["POST"])
@token_required
def create_tfsession():
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    core_channel = grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(core_channel)

    req = cloudServiceDAO_pb2.GetUserVCDRequest(user_id=request.form["user_id"])
    res = stub.getUserVCD(req)

    if not res:
        return jsonify(
            {"status": "ERROR", "message": "No se encontró usuario registrado"}
        )
    # Posibles fechas de expiración:
    # - 1 Hora = 60
    # - 1 día = 1440
    # - 1 Semana = 10080
    # - 1 Mes = 43830
    # - Por siempre = 999999999 (1900 años)
    token = jwt.encode(
        {
            "public_id": res.user.public_id,
            "exp": datetime.datetime.utcnow()
            + datetime.timedelta(minutes=int(requests.form["expiration_time"])),
        },
        app.config["TF_SECRET_KEY"],
        "HS256",
    )

    return jsonify({"status": "SUCCESS", "token": token})


@app.route("/session", methods=["POST"])
def create_session():
    auth = request.authorization  # Autenticación simple: usuario@tenant + contraseña

    if not auth or not auth.username or not auth.password:
        return make_response(
            "could not verify",
            401,
            {"Authentication": 'Se requieren datos de autenticación"'},
        )

    full_username = re.match(
        "(.*)@(.*)", auth.username
    )  # Validación de formato 'usuario@tenant'

    if not full_username:
        return jsonify(
            {"message": "Se espera formato 'usuario@organización' para autenticación"}
        )

    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()
    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    core_channel = grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(core_channel)
    req = cloudServiceDAO_pb2.GetOrgVCDRequest(name=full_username[2])
    org = stub.getOrgVCD(req)

    if not org:
        return jsonify(
            {
                "message": f"No se encuentra registrada la organización con nombre '{full_username[2]}'"
            }
        )
    elif len(org.orgs) > 1:
        # No deben de existir organizaciones con un mismo nombre
        return jsonify(
            {
                "message": f"Se encontraron múltiples organizaciones con nombre '{full_username[2]}' al intentar validar usuario, ésto no debería de ser posible e impide reconocer el usuario al que desea hacer referencia. Reporte éste error"
            }
        )

    req = cloudServiceDAO_pb2.GetUserVCDRequest(
        user_name=full_username[1]
    )  # Se requiere modificación para filtrado múltiple (Añadir org_id)
    user_found = stub.getUserVCD(req)

    if not user_found:
        return jsonify(
            {
                "message": f"No se encuentra registro de usuario con nombre '{full_username[1]}'"
            }
        )

    for uf in user_found.users:
        if uf.org_id == org.orgs[0].OrgVcdId:
            user_found = uf
            break

    if check_password_hash(user_found.password, auth.password):
        token = jwt.encode(
            {
                "public_id": user_found.public_id,
                "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
            },
            app.config["SECRET_KEY"],
            "HS256",
        )

        return jsonify({"token": token})

    return make_response(
        "could not verify",
        401,
        {"Authentication": "No se autenticó correctamente"},
    )


@app.route("/category_services", methods=["GET"])
# Recupera tipos de productos/servicios disponibles
@token_required
def get_product_type(token_required):
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetProductTypeRequest()
    try:
        res = stub.getProductType(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.products
            ],
        }
    )


@app.route("/services", methods=["GET"])
# Recupera catálogo de servicios filtrados por tipo de producto/servicio obtenidos desde petición /category_services
# Formulario: service_type_id
@token_required
def get_services(token_required):
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetServicesByProductTypeRequest(
        IdProductType=int(request.form["service_type_id"])
    )
    try:
        res = stub.getServicesCatalog(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": json.loads(res.response.response),
        }
    )


@app.route("/plans", methods=["GET"])
# Recupera catálogo de planes filtrados por servicio obtenidos desde petición /services
# Formulario: service_id, name
# @token_required
def get_plans(token_required):
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )

    filter_name = "name" if "name" in request.form else "service_id"
    filter_value = (
        request.form["name"]
        if filter_name == "name"
        else int(request.form["service_id"])
    )

    print(filter_name)
    print(filter_value)

    req = cloudServiceDAO_pb2.GetGenericInformationRequest(
        tab_name="plans", wcol_name_filter=filter_name, w_param=str( filter_value )
    )
    try:
        res = stub.getGenericInformation(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [json.loads(res.response.response)],
        }
    )


@app.route("/private/vcd/vdcs", methods=["GET"])
@token_required
def get_vdcs():
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetGenericInformationRequest(
        tab_name="vcd_vdc", w_param="name", wcol_name_filter=request.form["name"]
    )
    try:
        res = stub.getGenericInformation(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [json.loads(res.response.response)],
        }
    )


@app.route("/private/vcd/orgs", methods=["GET"])
@token_required
def get_orgs():
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetOrgVCDRequest(name=request.form["name"])
    try:
        res = stub.getOrgVCD(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.orgs
            ],
        }
    )


@app.route("/private/vcd/users", methods=["GET"])
@token_required
def get_users():
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetUserVCDRequest(
        user_name=request.form["username"], org_id=int(request.form["org_id"])
    )
    try:
        res = stub.getUserVCD(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.users
            ],
        }
    )


@app.route("/provisioning", methods=["POST"])
# Crea un registro en contracted_services equivalente a una contratación de servicio
# Formulario: plan_id, user_id, vdc_urn, vdc_name, subscription_id
@token_required
def provide():
    registered_vdc = True
    vdc_vcd_id = None
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.getfieldsvdcRequest(urn_id=request.form["vdc_urn"])
    try:
        res = stub.getfieldsvdc(req)
        vdc_vcd_id = res
    except Exception as e:
        registered_vdc = False

    if not registered_vdc:
        req = cloudServiceDAO_pb2.GetUserVCDRequest(
            user_id=int(request.form["user_id"])
        )
        try:
            res = stub.getUserVCD(req)
        except Exception as e:
            return jsonify(
                {
                    "status": "ERROR",
                    "message": f"{e}. Fallo de servicios hacia base de datos",
                }
            )

        req = cloudServiceDAO_pb2.setfieldsvdcRequest(
            name=request.form["vdc_name"],
            urn_id=request.form["vdc_urn"],
            subscription_id=int(request.form["subscription_id"]),
            org_id=res.users[0].org_id,
            active=True,
        )
        try:
            res = stub.setfieldsvdc(req)
        except Exception as e:
            return jsonify(
                {
                    "status": "ERROR",
                    "message": f"{e}. Fallo de servicios hacia base de datos",
                }
            )
        vdc_vcd_id = res.response.id
    else:
        vdc_vcd_id = vdc_vcd_id.fieldvdc[0].id

    req = cloudServiceDAO_pb2.SetContractedServicesRequest(
        plan_id=int(request.form["plan_id"]),
        user_id=int(request.form["user_id"]),
        vdc_vcd_id=vdc_vcd_id,
    )
    res = stub.setContractedServicesService(req)

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": json.loads(res.response.response)[0],
        }
    )


@app.route("/services_contracted", methods=["GET"])
# Recupera relación de un usuario con sus servicios contratados. El identificador de usuario se recupera desde front
# Formulario: user_id
@token_required
def get_contracted():
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetAllContractedServicesRequest(
        idUser=int(request.form["user_id"])
    )
    try:
        res = stub.getServicesAllContracted(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.contractedService
            ],
        }
    )

@app.route("/orgVDCs", methods=["GET"])
# @token_required
def get_orgvdcs():
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    # Conexión al canal de comunicación con microservicio DAO
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    req = cloudServiceDAO_pb2.GetOrgVCD_ServiceRequest(idservice=6)

    print(req)

    try:
        res = stub.getOrgVCD_Service(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                ### CAMBIOS A DAVID for x in res.contractedService
                for x in res.response
            ],
        }
    )

@app.route("/service/cbc/manage_subscription/<action>", methods=["POST"])
@token_required
def manage_subscription(token_required, action):
    """
    NextGen Antivirus - Carbon Black Cloud. Contratación y cancelación de servicio para Tenant/Organización específica por toma-pedidos dirigido área de mesa de servicio.
    action: contracting, cancel
    métodos: POST
    retorno: json -> status, message
    """
    with open(PATH_APP + "/App/certs/python_carbonblackmanager/ca.pem", "rb") as fp:
        ca_cert = fp.read()
    creds = grpc.ssl_channel_credentials(ca_cert)
    # Canal de comunicación hacia microservicio de carbon black
    cbc_stub = CBCManager_pb2_grpc.CBCManagerServiceStub(
        grpc.secure_channel(f"{CBC_HOST}:{CBC_PORT}", credentials=creds)
    )
    # Canal de comunicación hacia microservicio de DAO
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds_dao = grpc.ssl_channel_credentials(ca_cert)

    dao_stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds_dao)
    )

    if action != "contracting":
        return jsonify(
            {"status": "ERROR", "message": f"Acción solicitada '{action}' inválida"}
        )

    org_vdc = request.form["org_vdc"]
    # Formato esperado: {"href":"http://localhost", "guestOs":"Linux", "name":"vmlinux", "ownerName": "david", "dateCreated": "14-10-23"}
    licenses = request.form.getlist("licenses[]")
    number_licenses = request.form["number_licenses"]

    """Se inserta registro de servicio contratado. El aprovisionamiento debe de ser dummy debido a que no depende de nosotros en su totalidad, se generan registros para todas las licencias contratadas y se relacionan al servicio recién contratado."""
    req = cloudServiceDAO_pb2.SetContractedServicesRequest(
        plan_id=int(request.form["plan_id"]),
        user_id=int(request.form["user_id"]),
        vdc_vcd_id=int(request.form["vdc_vcd_id"]),
    )
    try:
        res = dao_stub.setContractedServicesService(req)
    except:
        return (
            jsonify(
                {
                    "status": "ERROR",
                    "message": "No fue posible registrar contratación de servicio, solicite que se registre manualmente y reporte éste error",
                }
            ),
        )

    csid = json.loads(res.response.response)[0]["id"]
    requested_licenses = len(licenses)

    for x in range(1, number_licenses + 1):
        # Licencia sin máquina virtual no definida
        empty_license = x > requested_licenses
        license = json.loads(licenses[x]) if not empty_license else ""
        req = cloudServiceDAO_pb2.SetLicensesRequest(
            name=f"{org_vdc}{x}",  # meramente descriptivo
            contracted_services_id=str(csid),  # relación para contabilidad
            specs=str(
                {
                    "api_host": "",  # se espera de mesa de servicio
                    "token": "",  # se espera de mesa de servicio
                    "license_count": f"{x}/{number_licenses}",
                    "data": [
                        {
                            "version": 0,
                            "vm_name": (
                                license["name"] if not empty_license else license
                            ),
                            "vm_ownerName": (
                                license["ownerName"] if not empty_license else license
                            ),
                            "vm_vdcName": org_vdc if not empty_license else license,
                            "vm_dateCreated": (
                                license["dateCreated"] if not empty_license else license
                            ),
                            "vm_href": (
                                license["href"] if not empty_license else license
                            ),
                            "sensor_name": "",
                            "sensor_loginUserName": "",
                            "sensor_registeredTime": "",
                            "sensor_deviceType": "",
                            "sensor_osVersion": "",
                            "sensor_sensorVersion": "",
                            "sensor_macAddress": "",
                            "sensor_deviceId": "",
                        }
                    ],
                }
            ),
        )  # formato por defecto para licencias de carbon black cloud

        try:
            res = dao_stub.setLicensesService(req)
        except:
            return (
                jsonify(
                    {
                        "status": "ERROR",
                        "message": f"Falló registro de licencia #{x}. La petición para contratación se realizó exitosamente, sin embargo no se registraron {number_licenses - x} licencia(s) en nuestra base de datos. Reporte éste error y solicite que se registren las licencias faltantes",
                    }
                ),
            )

        license_forms.append(
            CBCManager_pb2.LicenseForm(
                orgVdc=org_vdc,
                movementType=CBCManager_pb2.MovementType.INSTALL,
                hrefVM=license["href"],
                os=license["guestOs"],
                licenseId=int(json.loads(res.response.response)[0]["id"]),
                deviceId=0,
            )
        )

    req = cloudServiceDAO_pb2.SetUpdateRequestQueueRequest(
        contracted_services_id=csid,
        resource_specs='{"planid": "31","name_app_service": "","user_service": "","password_service": "","select_versions_srv": "","tags_service": ""}',
        request_datetime=(datetime.now()).strftime("%Y-%m-%d %H:%M:%S"),
        supply_status_id=1,
    )
    try:
        res = dao_stub.setUpdateRequestQueue(req)
    except:
        return (
            jsonify(
                {
                    "status": "ERROR",
                    "message": "No fue posible registrar contratación de servicio, solicite que se registre manualmente y reporte éste error",
                }
            ),
        )

    request_queue_id = json.loads(res.response.response)[0]["id"]

    req = CBCManager_pb2.ServiceManagementRequest(
        service_form=CBCManager_pb2.ServiceForm(
            service="Next Gen Antivirus",
            movementType=CBCManager_pb2.MovementType.CONTRACTING,
            plan=request.form["plan"],  # nombre de plan
            orgVdc=org_vdc,  # nombre de orgVDC
            paymentMethod=request.form[
                "payment_model"
            ],  # Se espera que siempre sea el valor estático 'Cobro único/anual'
            licenses=number_licenses,  # número de licencias
            expireDate=request.form["expire_date"],  # vacío en contratación
            requestQueueId=request_queue_id,
        ),
        licenses=license_forms,
    )
    try:
        res = cbc_stub.ServiceManage(req)
    except:
        return (
            jsonify(
                {
                    "status": "ERROR",
                    "message": f"No fue posible solicitar acciónn vía toma-pedido",
                }
            ),
        )

    if not res.status == "SUCCESS":
        return (
            jsonify(
                {
                    "status": "ERROR",
                    "message": f"{res.message}: No fue posible realizar solicitud para {'contratación' if action == 'contracting' else 'cancelación'} de servicio. Intente nuevamente o reporte éste error",
                }
            ),
        )

    return jsonify({"status": "SUCCESS", "message": action})



@app.route("/service/cbc/manage_sensor/<action>", methods=["POST"])
@token_required
def manage_sensor(token_required, action):
    """
    NextGen Antivirus - Carbon Black Cloud. Instalación, desinstalación y actualización de estatus de sensores en máquinas virtuales de VCD por toma-pedidos dirigido área de mesa de servicio (excepto update).
    action: install, uninstall, update_status
    métodos: POST
    args: *license_id, *request_queue_id,
    retorno: json -> status, message
    """
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)

    # Canal de comunicación hacia microservicio de Carbon Black Cloud
    cbc_stub = CBCManager_pb2_grpc.CBCManagerServiceStub(
        grpc.secure_channel(f"{CBC_HOST}:{CBC_PORT}", credentials=creds)
    )
    # Canal de comunicación hacia microservicio de DAO
    dao_stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )

    # En caso de acciones posteriores a contratación de servicio (instalación/desinstalación de licencias)
    license_id = request.form["license_id"]
    license_id = int(license_id) if license_id != "" else 0
    # En caso de acciones solicitadas en simultáneo a la contratación del servicio (instalación de licencias al momento de contratar servicio)
    request_queue_id = request.form["request_queue_id"]
    request_queue_id = int(request_queue_id) if request_queue_id != "" else 0

    if not license_id and request_queue_id:
        # Recupera información alternativa para consulta de licencias relacionadas a servicio aprovisionado (contracted_service)
        req = cloudServiceDAO_pb2.GetRequestQueueRequest(
            request_queue_id=request_queue_id
        )
        res = dao_stub.getRequestQueueService(req)

        if not res.requestqueue:
            return (
                jsonify(
                    {
                        "status": "ERROR",
                        "message": f"{res.message}: No fue posible recuperar registro de servicio solicitado, verifique que el valor enviado en request_queue_id es correcto y existe en la base de datos.",
                    }
                ),
            )

        res = res.requestqueue[0]

    # Recupera registros de licencia (valores en cero se ignoran por microservicio)
    req = cloudServiceDAO_pb2.GetLicensesRequest(
        contracted_services_id=res.contracted_services_id if request_queue_id else 0,
        idlicenses=license_id,
    )
    res = dao_stub.GetLicenses(req)

    if not res.licenses:
        return (
            jsonify(
                {
                    "status": "ERROR",
                    "message": f"{res.message}: No fue posible recuperar registros de licencias, verifique que el valor filtrado sea correcto y existe en la base de datos.",
                }
            ),
        )
    # Registros de licencias extraías de BD
    licenses = res.licenses
    # JSON con información completa de licencia
    license_specs = {}
    # JSON con datos de última versión de licencia
    license_version = {}

    for l in licenses:
        license_specs = l.specs
        license_specs = license_specs.replace("'", '"')
        license_specs = json.loads(license_specs)
        # Datos de última versión de licencia
        license_version = license_specs["data"][len(license_specs["data"]) - 1]

        if not license_id and request_queue_id and license_version["vm_name"] == "":
            # Se recupera primer registro de de licencia libre (sin asignación)
            licenses = l
            license_id = l.idlicenses
            break

    if action != "update_status":
        # Formulación y envío de toma-pedidos
        req = CBCManager_pb2.SensorManagementRequest(
            license_form=CBCManager_pb2.LicenseForm(
                orgVdc=request.form["org_vdc"],
                movementType=CBCManager_pb2.MovementType.INSTALL
                if action == "install"
                else CBCManager_pb2.MovementType.UNINSTALL,
                # Se espera un arreglo desde el formulario de la petición http
                hrefVM=request.form["href"],
                os=request.form["guestOs"],
                licenseId=license_id,
            )
        )
        res = cbc_stub.SensorManage(req)

        if res.status != "SUCCESS":
            return (
                jsonify(
                    {
                        "status": "ERROR",
                        "message": f"{res.message}: No fue posible solicitar {'instalación de sensores' if action == 'install' else 'desinstalación de sensores' if action == 'uninstall' else 'actualización de estatus de sensor'}. Intente nuevamente o reporte éste error",
                    }
                ),
            )

    if action == "install":
        # información de máquina virtual sobre la que se instalará sensor
        license_version["vm_name"] = request.form["name"]
        license_version["vm_ownerName"] = request.form["ownerName"]
        # lastest_version["vm_vdcName"] = request.form["vdcName"]
        license_version["vm_dateCreated"] = request.form["date_created"]
        license_version["vm_href"] = request.form["href"]

        req = cloudServiceDAO_pb2.SetUpdateLicensesRequest(
            idlicenses=licenses.idlicenses,
            name=licenses.nameLicenses,
            specs=str(license_specs),
            id_contracted_services=licenses.contracted_services_id,
        )

        res = dao_stub.setUpdateLicenses(req)

        if (
            not res.response.response
            or not "result" in res.response.response
            or json.loads(res.response.response)[0]["result"] != "exito"
        ):
            return (
                jsonify(
                    {
                        "status": "ERROR",
                        "message": "No fue posible actualizar registro de licencia con datos de Máquina Virtual objetivo. Reporte éste error.",
                    }  # No se recupera detalle de error debido a que la respuesta no da detalle de errores, sólo retorna el mensaje 'error' 02/08/2023
                ),
            )

        return (
            jsonify(
                {
                    "status": "SUCCESS",
                    "message": "Solicitud para instalación de sensor exitosa. Espere respuesta a su solicitud desde el portal de administración.",
                }
            ),
        )

    if action == "uninstall":
        return (
            jsonify(
                {
                    "status": "SUCCESS",
                    "message": "Solicitud para desinstalación de sensor exitosa. Espere respuesta a su solicitud desde el portal de administración.",
                }
            ),
        )

    if action == "update_status":
        # se recupera regitro(s) de licencia y se realiza petición para actualizar estatus
        cbc_api_host = (
            res.licenses.specs.api_host
        )  # se requiere host recuperado de tabla de licencias
        cbc_token = (
            res.licenses.specs.token
        )  # se requiere token reuperado de tabla de licencias

        req = CBCManager_pb2.SensorManagementRequest(
            cbc_api_host=cbc_api_host,
            auth_token=cbc_token,
            sensor_action=request.form["sensor_action"],  # duplicar valores enum
            licenses=[
                CBCManager_pb2.LicenseForm(
                    orgVdc=request.form["org_vdc"],
                    movementType=CBCManager_pb2.MovementType.UPDATE_STATUS,
                    # Se espera un arreglo desde el formulario de la petición http
                    hrefVM=request.form["href"],
                    os=request.form["guestOs"],
                    licenseId=license_id,
                ),
            ],
        )

        res = cbc_stub.SensorManage(req)
        """
        Nota. Es posible desinstalar sensores y borrar registros visibles en el portal desde la API con los action_type UNINSTALL_SENSOR y DELETE_SENSOR respectivamente, sin embargo, son procesos asíncronos, es decir que no se espera una respuesta al efectuar la acción, por lo que podemos enviar una desinstalación y borrar registro inmediatamente, lo cuál no nos da la certeza de una desinstalación completada exitosamente.
        """
        return jsonify(
            {
                "status": "SUCCESS",
                "message": f"Establecido estatus {request.form['action_type']}",
            }
        )


@app.route(
    "/service/cbc/manage_user_accounts/<action>",
    methods=["POST", "GET", "PUT", "DELETE"],
)
@token_required
def manage_user_accounts(action):
    """
    NextGen Antivirus - Carbon Black Cloud. Creación, recuperación, actualización y borrado de cuentas de usuario con rol fijo de Analista 3.
    action: add, get, change, delete
    métodos: POST, GET, PUT, DELETE
    retorno: json -> status, message
    """
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)

    # Canal de comunicación hacia microservicio de carbon black
    cbc_stub = CBCManager_pb2_grpc.CBCManagerServiceStub(
        grpc.secure_channel(f"{CBC_HOST}:{CBC_PORT}", credentials=creds)
    )
    # Canal de comunicación hacia microservicio de DAO
    dao_stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )
    # Se recupera información de licencias necesaria para realizar peticiones a API de CBC
    req = cloudServiceDAO_pb2.GetLicensesRequest(
        org_id=request.form["org_id"], idService=request.form["service_id"]
    )
    res = dao_stub.GetLicenses(req)

    if not res:
        return jsonify(
            {
                "status": "SUCCESS",
                "message": "No se encontraron licencias relacionadas a usuario, revise si la información es correcta o reporte éste error",
            }
        )

    api_host = res.api_host  # sólo podemos recuperarlo de tabla de licencias
    token = res.token  # sólo podemos recuperarlo de tabla de licencias

    try:
        res = requests.get(
            f"{api_host}/appservices/v5/orgs",
            timeout=120,
            headers={"X-AUTH-TOKEN": token},
        )
        res.raise_for_status()
    except:
        return jsonify(
            {
                "status": "ERROR",
                "message": "No fue posible recuperar org key de ambiente de carbon black",
            }
        )

    org_key = res.organization.orgKey
    org_id = res.organization.id

    if request.method == "POST" and action == "add":
        req = CBCManager_pb2.CreateUserAccountRequest(
            auth_token=token,
            cbc_api_host=api_host,
            org_id=org_id,
            org_key=org_key,
            user_data=CBCManager_pb2.UserAccount(
                contact_id=request.form["contact_id"],
                email=request.form["email"],
                first_name=request.form["first_name"],
                last_name=request.form["last_name"],
                login_id=request.form["login_id"],
                phone=request.form["phone"],
            ),
        )
        res = cbc_stub.CreateUserAccount(req)

    if request.method == "GET" and action == "get":
        req = CBCManager_pb2.UserAccountsRequest(
            auth_token=token, cbc_api_host=api_host, org_key=org_key
        )
        res = cbc_stub.GetUserAccounts(req)

    if request.method == "PUT" and action == "change":
        req = CBCManager_pb2.UserAccountsRequest(
            auth_token=token,
            cbc_api_host=api_host,
            org_key=org_key,
        )
        res = cbc_stub.GetUserAccounts(req)

        req = CBCManager_pb2.UpdateUserAccountRequest(
            auth_token=token,
            cbc_api_host=api_host,
            org_key=org_key,
            login_name=request.form["login_name"],
            org_id=org_id,
            user_data=CBCManager_pb2.UserAccount(
                contact_id=res[1].contact_id,
                email=request.form["login_name"],
                first_name=request.form["first_name"],
                last_name=request.form["last_name"],
                login_id=res[1].login_id,
                phone=request.form["phone"],
            ),
        )
        res = cbc_stub.UpdateUserAccount(req)

    if request.method == "DELETE" and action == "delete":
        req = CBCManager_pb2.UserAccountsRequest(
            auth_token=token,
            cbc_api_host=api_host,
            org_key=org_key,
        )
        res = cbc_stub.GetUserAccounts(req)

        req = CBCManager_pb2.DeleteUserAccountRequest(
            auth_token=token,
            cbc_api_host=api_host,
            login_id=res[1].login_id,
            org_key=org_key,
        )
        res = cbc_stub.UpdateUserAccount(req)

    return jsonify(res)


@app.route("/service/cbc/retrieve/vms", methods=["GET"])
@token_required
def get_vms(token_required):
    """
    NextGen Antivirus - Carbon Black Cloud. Recupera listado de máquinas virtuales en formato XML, purga información que puede cambiar en un futuro y se retorna en formato JSON.
    métodos: GET
    retorno: json -> status, message, json
    """

    var_org_id = (
        request.args["org_id"] if len(request.args.getlist("org_id")) > 0 else 0
    )

    var_org_vdc = (
        request.args["org_vdc"] if len(request.args.getlist("org_vdc")) > 0 else None
    )

    with open(PATH_APP + "/App/certs/python_carbonblackmanager/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    credentials = grpc.ssl_channel_credentials(ca_cert)
    # Canal de comunicación hacia microservicio de carbon black
    cbc_stub = CBCManager_pb2_grpc.CBCManagerServiceStub(
        grpc.secure_channel(f"{CBC_HOST}:{CBC_PORT}", credentials=credentials)
    )
    # Canal de comunicación hacia microservicio de DAO
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds_dao = grpc.ssl_channel_credentials(ca_cert)

    dao_stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds_dao)
    )

    req = cloudServiceDAO_pb2.GetOrgVCDRequest(org_id=int(var_org_id))

    try:
        org = dao_stub.getOrgVCD(req)
    except Exception as e:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"{e}. Fallo de servicios hacia base de datos",
            }
        )

    if not org:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"No se encuentra registrada la organización indicada",
            }
        )
    elif len(org.orgs) > 1:
        # No deben de existir organizaciones con un mismo nombre
        return jsonify(
            {
                "status": "ERROR",
                "message": f"Se encontraron múltiples organizaciones con el identificador entregado al intentar validar usuario, ésto no debería de ser posible e impide reconocer el usuario al que desea hacer referencia. Reporte éste error",
            }
        )

    org = org.orgs[0]

    req = CBCManager_pb2.RetrieveVMsRequest(
        vdc_id=var_org_vdc,  # Se envía desde front
        vcd_api_host=org.host,
        auth=base64.b64decode(
            vcd_service_account
        ),  # Se esperan credenciales en formato basic (antes de cifrado) o bearer token. Revisar origen para obtención, posiblemente se requiera una cuenta de servicio por organización. BD -> user_vcd -> basic base64(uservcd@org:passwordh265)
        is_basic_auth=True,  # Se requiere validación o se eliminará en caso de implementar cuenta de servicio
        api_version=org.api_version,
        tenant=org.name,
    )

    res = cbc_stub.RetrieveVMs(req)

    if res.status == "ERROR":
        return jsonify(
            {
                "status": "ERROR",
                "message": res.message,
            }
        )

    return MessageToJson(res)


@app.route("/service/cbc/retrieve/sensors", methods=["GET"])
@app.route("/service/cbc/retrieve/sensors/<deviceid>", methods=["GET"])
@token_required
def get_sensors(token_required, deviceid):
    """
    NextGen Antivirus - Carbon Black Cloud. Recupera listado de sensores de tipo Workload en formato CSV, purga información que pudiese cambiar en un futuro y se retorna en formato JSON.
    métodos: GET
    retorno: json -> status, message, json
    """
    with open(PATH_APP + "/App/certs/csharp_dao/ca.pem", "rb") as fp:
        ca_cert = fp.read()
    # CAMBIO: Recuperamos sólo un sensor filtrado por device_id retornando la info en formato json
    creds = grpc.ssl_channel_credentials(ca_cert)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(
        grpc.secure_channel(f"{DAO_HOST}:{DAO_PORT}", credentials=creds)
    )

    id_srv_con = int(request.args["contracted_services_id"])

    print(id_srv_con)

    req = cloudServiceDAO_pb2.GetLicensesRequest(
        contracted_services_id=id_srv_con
    )
    res = stub.GetLicenses(req)

    if not res:
        return jsonify(
            {
                "status": "ERROR",
                "message": "No se encontraron licencias dadas de alta, revise si la información es correcta o reporte éste error",
            }
        )
    
    print(res)
    spects = eval(res.licenses[0].specs)

    api_host = spects['api_host']

    token = spects['token']

    with open(PATH_APP + "/App/certs/python_carbonblackmanager/ca.pem", "rb") as fp:
        ca_cert = fp.read()
    creds_cb = grpc.ssl_channel_credentials(ca_cert)

    stub = CBCManager_pb2_grpc.CBCManagerServiceStub(
        grpc.secure_channel(f"{CBC_HOST}:{CBC_PORT}", credentials=creds_cb)
    )
    req = CBCManager_pb2.RetrieveSensorsRequest(
        cbc_api_host=api_host, auth_token=token
    )
    res = stub.RetrieveWorkloadSensors(req)

    print((res))
    return MessageToJson(res)


@app.route(
    "/service/daas/manage_contracts/<action>",
    methods=["POST", "DELETE"],
)
@token_required
def manage_contracts(action):
    if not "request_queue_id" in request.form:
        return jsonify({"status": "ERROR", "message": "No se recibió request_queue_id"})
    # delete, pause, reactivate
    if request.method == "DELETE" and action == "delete":
        request_queue_id = int(request.form["request_queue_id"])

        result = update_contract_status(request_queue_id, STATUS.DELETED)

    if request.method == "POST" and action == "pause":
        request_queue_id = int(request.form["request_queue_id"])

        result = update_contract_status(request_queue_id, STATUS.INACTIVE)

    if request.method == "POST" and action == "reactivate":
        request_queue_id = int(request.form["request_queue_id"])

        result = update_contract_status(request_queue_id, STATUS.ACTIVE)

    return jsonify(result)


@app.route(
    "/service/daas/deployments/<id>",
    methods=["GET"],
)
@token_required
def get_deployments(id):
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # canal de comunicación hacía microservicio cloudServiceDAO (BD)
    ms_channel = grpc.secure_channel(f"{DAO_HOST}:443", credentials=creds)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(ms_channel)

    if not id:
        return jsonify(
            {
                "status": "ERROR",
                "message": "Se requiere request_queue_id",
                "data": [],
            }
        )
    else:
        req = cloudServiceDAO_pb2.GetRequestQueueRequest(request_queue_id=int(id))
        try:
            res = stub.getRequestQueueService(req)
        except grpc._channel._InactiveRpcError as e:
            return jsonify(
                {
                    "status": "ERROR",
                    "message": str(e),
                    "data": [],
                }
            )

    if not res.requestqueue:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"No fue posible encontrar registro con request_queue_id={id}. Verifique que la información proporcionada es correcta o reporte éste error.",
                "data": [],
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.requestqueue
            ],
        }
    )


@app.route(
    "/service/daas/deployments/contract/<contracted_services_id>",
    methods=["GET"],
)
@token_required
def get_deployment_contract(contracted_services_id):
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # canal de comunicación hacía microservicio cloudServiceDAO (BD)
    ms_channel = grpc.secure_channel(f"{DAO_HOST}:443", credentials=creds)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(ms_channel)

    if not contracted_services_id:
        return jsonify(
            {
                "status": "ERROR",
                "message": "Se requiere contracted_service_id",
                "data": [],
            }
        )
    else:
        req = cloudServiceDAO_pb2.GetRequestQueueRequest(
            contracted_services_id=int(contracted_services_id)
        )
        try:
            res = stub.getRequestQueueService(req)
        except grpc._channel._InactiveRpcError as e:
            return jsonify(
                {
                    "status": "ERROR",
                    "message": str(e),
                    "data": [],
                }
            )

    if not res.requestqueue:
        return jsonify(
            {
                "status": "ERROR",
                "message": f"No fue posible encontrar registro con contracted_service_id={contracted_services_id}. Verifique que la información proporcionada es correcta o reporte éste error.",
                "data": [],
            }
        )

    return jsonify(
        {
            "status": "SUCCESS",
            "message": "",
            "data": [
                json.loads(MessageToJson(x, including_default_value_fields=True))
                for x in res.requestqueue
            ],
        }
    )


def update_contract_status(request_queue_id, status: STATUS):
    with open("ca.pem", "rb") as fp:
        ca_cert = fp.read()

    creds = grpc.ssl_channel_credentials(ca_cert)
    # canal de comunicación hacía microservicio cloudServiceDAO (BD)
    ms_channel = grpc.secure_channel(f"{DAO_HOST}:443", credentials=creds)
    stub = cloudServiceDAO_pb2_grpc.servicioDAOServiceStub(ms_channel)
    # se requiere recuperar registro de request_queue a modificar, debido al requisito de actualización de todas las columnas por registro. Requerido asi por DAO.
    req = cloudServiceDAO_pb2.GetRequestQueueRequest(request_queue_id=request_queue_id)
    res = stub.getRequestQueueService(req)

    if not res.requestqueue:
        return {
            "status": "ERROR",
            "message": f"No fue posible     encontrar registro con request_queue_id={request_queue_id}. Verifique que la información proporcionada es correcta o reporte éste error.",
        }

    request_contract = res.requestqueue[0]  # registro a editar de requests_queue

    r_datetime = request_contract.request_datetime
    r_datetime = (
        r_datetime
        if r_datetime == "" or r_datetime is None
        else patch_dateformat(r_datetime)
    )
    s_datetime = request_contract.supply_datetime
    s_datetime = (
        s_datetime
        if s_datetime == "" or r_datetime is None
        else patch_dateformat(s_datetime)
    )
    d_datetime = request_contract.down_datetime
    d_datetime = (
        s_datetime
        if s_datetime == "" or r_datetime is None
        else patch_dateformat(s_datetime)
    )

    req = cloudServiceDAO_pb2.SetUpdateRequestQueueRequest(
        request_queue_id=request_queue_id,
        contracted_services_id=request_contract.contracted_services_id,
        resource_specs=request_contract.resource_specs,
        request_datetime=r_datetime,
        supply_datetime=s_datetime,
        down_datetime=d_datetime,
        supply_status_id=request_contract.supply_status_id,
        service_status_id=status.value,
        error_log=request_contract.error_log,
        identificator=request_contract.identificator,
        name_app=request_contract.name_app,
        status=request_contract.status,
        port=request_contract.port,
    )  # DELETED
    res = stub.setUpdateRequestQueue(req)

    if (
        not res.response.response
        or not "result" in res.response.response
        or json.loads(res.response.response)[0]["result"] != "exito"
    ):
        return {
            "status": "ERROR",
            "message": f"No fue posible actualizar estatus para registro con request_queue_id={request_queue_id}. Reporte éste error.",
        }

    return {
        "status": "SUCCESS",
        "message": "Se actualizó estatus correctamente. Se aplicarán los cambios de estatus en breve.",
    }


def patch_dateformat(datetime_str):
    try:
        datetime_str = datetime.strptime(datetime_str, "%m/%d/%Y %I:%M:%S %p")
    except ValueError:
        datetime_str = datetime.strptime(datetime_str, "%d/%m/%Y %H:%M:%S")

    return datetime_str.strftime("%Y-%m-%d %H:%M:%S.%f")


@app.route("/services_security", methods=["GET"])
@token_required
def services_security():
    if request.method == "GET":
        events_ids = (
            request.form["events_ids"]
            if len(request.form.getlist("events_ids")) > 0
            else "NOTFOUND"
        )
        if events_ids == "srv-apigateway":
            from App.main import AxwaySrv as axway

            return axway.main(request, PATH_APP)


@app.route("/wsoa", methods=["POST"])
@token_required
def wsoa():
    if request.method == "POST":
        events_ids = (
            request.form["events_ids"]
            if len(request.form.getlist("events_ids")) > 0
            else "NOTFOUND"
        )
        if events_ids == "srv-woa":
            from App.main import WSOA as wsoa

            return wsoa.main(request, PATH_APP)


@app.route("/kmshsm", methods=["POST"])
@token_required
def fortanix():
    if request.method == "POST":
        events_ids = (
            request.form["events_ids"]
            if len(request.form.getlist("events_ids")) > 0
            else "NOTFOUND"
        )
        if events_ids == "srv-fortanix":
            from App.main import Fortanix as fortanix

            return fortanix.main(request, PATH_APP)

@app.route('/srvcdn/<path:remaining_path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
@token_required
def cdn_api(token_required, remaining_path):
    #print(remaining_path)
    from CDN import CDN as cdn_api
    return cdn_api(request, PATH_APP, remaining_path).main()


@app.route("/hello",methods=["POST", "GET"])
@token_required
def hello(token_required):
    return f"HELLO FROM FLASK "

### 
from App.main.DaaS import DaaS
daas = DaaS(PATH_APP, DAO_HOST, DAO_PORT, token_required)
app.register_blueprint(daas.blueprint, url_prefix='/daas')
###
