import base64
from enum import Enum
import json
import os
import re
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side

import pandas as pd
import CBCManager_pb2
import CBCManager_pb2_grpc
import grpc
from concurrent import futures
import requests
from requests.exceptions import RequestException
import xml.etree.ElementTree as ET
from exchangelib import (
    DELEGATE,
    Credentials,
    Account,
    FileAttachment,
    Message,
    HTMLBody,
)

# **se requieren añadir a Dockerfile
SENDER_EMAIL = os.environ["CBC_CONSUMER_SENDER_EMAIL"]  # cuenta de correo remitente
SENDER_PASSWORD = os.environ["CBC_CONSUMER_SENDER_PASS"]  # contraseña de remitente
RECEIVER_EMAIL = os.environ[
    "CBC_CONSUMER_RECEIVER_EMAIL"
]  # cuenta de correo destinatario


class MovementType(Enum):
    CONTRACTING = 0
    CANCEL = 1
    INSTALL = 2
    UNINSTALL = 3
    UPDATE_STATUS = 4

    def _get_confirmation(self):
        c = {
            self.CONTRACTING: "La contratación de su servicio ha sido realizada, su servicio será activado en un plazo máximo de 48 horas.",
            self.CANCEL: "Se ha enviado la solicitud de cancelación de servicio, se evaluará la respuesta acorde al estatus de pagos actual de su servicio, el tiempo de respuesta máximo es de 48 horas.",
            self.INSTALL: "La instalación de sensor ha sido solicitada y será atendida en un no plazo no mayor a 48 horas.",
            self.UNINSTALL: "Se ha solicitado la desinstalación de un sensor y será atendida en un no plazo no mayor a 48 horas.",
        }
        return c[self]

    def _get_es_name(self):
        c = {
            self.CONTRACTING: "CONTRATACIÓN",
            self.CANCEL: "CANCELACIÓN",
            self.INSTALL: "INSTALACIÓN",
            self.UNINSTALL: "DESINSTALACIÓN",
            self.UPDATE_STATUS: "ACTUALIZACIÓN DE ESTATUS",
        }
        return c[self]


class CBCManagerService(CBCManager_pb2_grpc.CBCManagerServiceServicer):
    def GetUserAccounts(self, request, context):
        try:
            r = requests.get(
                f"{request.cbc_api_host}/appservices/v6/orgs/{request.org_key}/users",
                timeout=120,
                verify=False,
                headers={
                    "X-AUTH-TOKEN": request.auth_token,
                },
            )
            r.raise_for_status()
        except RequestException as e:
            return CBCManager_pb2.UserAccountsResponse(
                accounts=[],
                status="ERROR",
                message=f"No fue posible recuperar listado de usuarios desde consola de administración de Carbon Black. {e}",
            )

        users = r.json()["users"]
        user_accounts = []

        for u in users:
            if request.email and request.email != u["email"]:
                continue

            try:
                user_accounts.append(
                    CBCManager_pb2.UserAccount(
                        email=u["email"],
                        first_name=u["first_name"],
                        last_name=u["last_name"],
                        phone=u["phone"],
                        login_id=u["login_id"],
                        contact_id=u["contact_id"],
                    )
                )
            except KeyError as e:
                return CBCManager_pb2.UserAccountsResponse(
                    accounts=[],
                    status="ERROR",
                    message=f"No se encontró elemento en listado de cuenta de usuario, por favor notifique éste error. {e}",
                )
        return CBCManager_pb2.UserAccountsResponse(
            accounts=user_accounts, status="SUCCESS", message=None
        )

    def CreateUserAccount(self, request, context):
        try:
            if not request.user_data.email:
                return CBCManager_pb2.CreateUserAccountResponse(
                    status="ERROR",
                    message=f"Se espera campo obligatorio 'email'",
                )

            r = requests.post(
                f"{request.cbc_api_host}/appservices/v6/orgs/{request.org_key}/users",
                timeout=120,
                verify=False,
                headers={
                    "X-AUTH-TOKEN": request.auth_token,
                },
                json={
                    "org_id": request.org_id,
                    "email": request.user_data.email,
                    "role": "DEPRECATED",  # Único valor permitido
                    "role_urn": "psc:role::BETA_LEVEL_3_ANALYST",
                    "first_name": request.user_data.first_name,
                    "last_name": request.user_data.last_name,
                    "phone": request.user_data.phone,
                    "auth_method": "PASSWORD",
                    "profiles": [
                        {
                            "orgs": {"allow": [f"psc:org:{request.org_key}"]},
                            "roles": ["psc:role::ALL"],
                        }
                    ],
                },
            )
            r.raise_for_status()
        except RequestException as e:
            return CBCManager_pb2.CreateUserAccountResponse(
                status="ERROR",
                message=f"No fue posible dar de alta usuario con dirección {request.user_data.email}. {r.json()['message'] if r.status_code not in [500, 503] else e}",
            )

        return CBCManager_pb2.CreateUserAccountResponse(
            status="SUCCESS",
            message=f"Se ha enviado enlace para establecer contraseña a cuenta {request.user_data.email}",
        )

    def UpdateUserAccount(self, request, context):
        if (
            not request.user_data.email
            or not request.user_data.contact_id
            or not request.user_data.login_id
        ):
            return CBCManager_pb2.UpdateUserAccountResponse(
                status="ERROR",
                message=f"Se requieren todos los campos obligatorios",
            )

        try:
            r = requests.put(
                f"{request.cbc_api_host}/appservices/v6/orgs/{request.org_key}/users/{request.user_data.login_id}",
                timeout=120,
                verify=False,
                headers={
                    "X-AUTH-TOKEN": request.auth_token,
                },
                json={
                    "org_id": request.org_id,
                    "login_id": request.user_data.login_id,
                    "email": request.user_data.email,
                    "login_name": request.login_name,
                    "role": "DEPRECATED",  # Único valor permitido
                    "first_name": request.user_data.first_name,
                    "last_name": request.user_data.last_name,
                    "phone": request.user_data.phone,
                    "contact_id": request.user_data.contact_id,
                },
            )
            r.raise_for_status()  # Existe un bug al intentar cambiar la cuenta de correo al alias de una cuenta existente
        except RequestException as e:
            return CBCManager_pb2.UpdateUserAccountResponse(
                status="ERROR",
                message=f"No fue posible actualizar usuario con dirección {request.user_data.email}. {r.json()['message'] if r.status_code not in [500, 503] else e}",
            )

        return CBCManager_pb2.UpdateUserAccountResponse(
            status="SUCCESS",
            message=f"Se ha modificado usuario {request.user_data.email}",
        )

    def DeleteUserAccount(self, request, context):
        try:
            r = requests.delete(
                f"{request.cbc_api_host}/access/v2/orgs/{request.org_key}/grants/psc:user:{request.org_key}:{request.login_id}",
                timeout=120,
                verify=False,
                headers={
                    "X-AUTH-TOKEN": request.auth_token,
                },
            )  # Se requiere borrado de user grants
            r.raise_for_status()
        except RequestException as e:
            return CBCManager_pb2.DeleteUserAccountResponse(
                status="ERROR",
                message=f"No fue posible borrar 'user grants' de usuario. {e}",
            )

        try:
            r = requests.delete(
                f"{request.cbc_api_host}/appservices/v6/orgs/{request.org_key}/users/{request.login_id}",
                timeout=120,
                verify=False,
                headers={
                    "X-AUTH-TOKEN": request.auth_token,
                },
            )  # Se requiere borrado de user grants
            r.raise_for_status()
        except RequestException as e:
            return CBCManager_pb2.DeleteUserAccountResponse(
                status="ERROR",
                message=f"No fue posible borrar cuenta de usuario. {e}",
            )

        return CBCManager_pb2.DeleteUserAccountResponse(
            status="SUCCESS",
            message=f"Se borró exitosamente usuario",
        )

    def RetrieveVMs(self, request, context):
        if request.is_basic_auth:
            try:
                r = requests.post(
                    f"{request.vcd_api_host}/cloudapi/1.0.0/sessions",
                    timeout=120,
                    verify=False,
                    headers={
                        "Authorization": f"Basic {base64.b64encode(bytes(request.auth, 'utf-8')).decode()}",  # Se requiere formato username@tenant:password para autenticación básica, desde aquí se encripta a base64
                        "Accept": f"application/json;version={request.api_version}",
                    },
                )
                r.raise_for_status()
            except RequestException as e:
                return CBCManager_pb2.RetrieveVMsResponse(
                    vms=[],
                    status="ERROR",
                    message=f"No fue posible autenticar con credenciales de VCD, revise si la inforamción es correcta. {e}",
                )

        try:
            r = requests.get(
                f"{request.vcd_api_host}/api/query?type=vm&format=records&page=1&pageSize=999&filterEncoded=true&filter=((vdc=={request.vdc_id}))&sortDesc=dateCreated&fields=metadata@SYSTEM:vmware.cloud.director.availability.protected,metadata@GENERAL:vmware.cloud.director.ui.badges.blue,metadata@GENERAL:vmware.cloud.director.ui.badges.red,metadata@GENERAL:vmware.cloud.director.ui.badges.orange,metadata@GENERAL:vmware.cloud.director.ui.badges.green,metadata@GENERAL:vmware.cloud.director.ui.badges.magenta&links=false",
                timeout=120,
                verify=False,
                headers={
                    "Authorization": f"Bearer {r.headers['X-VMWARE-VCLOUD-ACCESS-TOKEN'] if request.is_basic_auth else request.auth}",
                    "Accept": f"application/*+xml;version={request.api_version}",
                    "Content-Type": "application/xml",
                },
            )
            r.raise_for_status()
        except RequestException as e:
            return CBCManager_pb2.RetrieveVMsResponse(
                vms=[],
                status="ERROR",
                message=f"No fue posible recuperar listado de máquinas virtuales desde VCD. {e}",
            )

        # root = ET.fromstring(re.sub(r'\n+', '', r.text))
        root = ET.fromstring(r.text)

        for x in root.iter():
            x.tag = x.tag.split("}")[
                -1
            ]  # Eliminamos prefijos al tag (A la fecha de visualizaba {http://www.vmware.com/vcloud/v1.5})

        vms = root.findall("VMRecord")

        if not vms:
            return CBCManager_pb2.RetrieveVMsResponse(
                vms=[],
                status="ERROR",
                message="No se encontraron servidores/MV listados de VCD, verifique si ésto es correcto, en caso contrario notifique éste error",
            )

        vms_data = []

        for vm in vms:
            m = re.search(r"vm-.*", vm.attrib["href"])

            if not m:
                return CBCManager_pb2.RetrieveVMsResponse(
                    vms=[],
                    status="ERROR",
                    message="No se encontró referencia a Máquina Virtual en VCD, notifique éste error",
                )

            href = f"{request.vcd_api_host}/tenant/{request.tenant}/vdcs/{request.vdc_id}/vm/{m.group()}"

            vms_data.append(
                CBCManager_pb2.VirtualMachine(
                    name=vm.attrib["name"],
                    ownerName=vm.attrib["ownerName"],
                    # vdcName=vm.attrib["vdcName"],
                    description=vm.attrib["description"],
                    guestOs=vm.attrib.get("guestOs", "NOT DEFINED"),
                    dateCreated=vm.attrib.get("dateCreated", "NOT DEFINED"),
                    href=href,
                    ### COMENTAR A DAVID SI ESTOS DATOS NO SIEMPRE SE RETORNAN SI NO INGRESAR ESTA VALIDACION
                    sensorId=request.sensor_id if hasattr(request, 'sensor_id') else 0, #request.sensor_id,  # identificadores de máquinas virtuales, si no existe o está vacío significa que no existe sensor instalado
                    licenseId=request.license_id if hasattr(request, 'license_id') else 0, #request.license_id,  # no se define si no existe un sensor relacionado, nos ayuda a tener la relación con la base de datos
                )
            )

        return CBCManager_pb2.RetrieveVMsResponse(
            vms=vms_data, status="SUCCESS", message=""
        )

    def RetrieveWorkloadSensors(self, request, context):
        job_id = 0
        sensors_array = []

        try:
            res = requests.get(
                f"{request.cbc_api_host}/appservices/v5/orgs",
                timeout=120,
                headers={"X-AUTH-TOKEN": request.auth_token},
            )
            res.raise_for_status()
        except:
            return CBCManager_pb2.RetrieveSensorsResponse(
                vmws=[],
                status="ERROR",
                message=f"No fue posible recuperar datos de organización. {e}",
            )

        org_key = json.loads(res.content)
        org_key = org_key["organization"]["orgKey"]
        device_id = request.device_id

        if device_id:
            try:
                r = requests.get(
                    f"{request.cbc_api_host}/appservices/v6/orgs/{org_key}/devices/{device_id}",
                    timeout=120,
                    verify=False,
                    headers={
                        "X-AUTH-TOKEN": request.auth_token,
                    },
                )
                r.raise_for_status()
            except requests.RequestException as e:
                return CBCManager_pb2.RetrieveSensorsResponse(
                    vmws=[],
                    status="ERROR",
                    message=f"No fue posible recuperar datos de sensor con identificador {device_id}. {e}",
                )

            test_array = [json.loads(r.content)]

            for ta in test_array:
                sensors_array.append(
                    CBCManager_pb2.VMWorkloadSensor(
                        name=ta["name"],
                        loginUserName=ta["login_user_name"],
                        firstName=ta["first_name"],
                        lastName=ta["last_name"],
                        status=ta["status"],
                        registeredTime=ta["registered_time"],
                        lastInternalIpAddress=ta["last_internal_ip_address"],
                        lastExternalIpAddress=ta["last_external_ip_address"],
                        osVersion=ta["os_version"],
                        sensorVersion=ta["sensor_version"],
                        macAddress=ta["mac_address"],
                        sensorOutOfDate=ta["sensor_out_of_date"],
                        deviceId=ta["id"],
                    )
                )

        else:
            try:
                r = requests.get(
                    f"{request.cbc_api_host}/appservices/v6/orgs/{org_key}/devices/_search/download?status=&deployment_type=WORKLOAD,VDI&sort_field=last_contact_time&sort_order=DESC&async=true",
                    timeout=120,
                    verify=False,
                    headers={
                        "X-AUTH-TOKEN": request.auth_token,
                    },
                    json={
                        "criteria": {"deployment_type": ["WORKLOAD", "VDI"]},
                        "sort": [{"field": "last_contact_time", "order": "DESC"}],
                        "start": 1,
                        "rows": 999,
                        "query": "",
                    },
                )
                r.raise_for_status()
                job = r.json()
            except RequestException as e:
                return CBCManager_pb2.RetrieveSensorsResponse(
                    vmws=[],
                    status="ERROR",
                    message=f"No fue posible generar listado de sensores VM Workload. {e}",
                )

            if job["status"] == "COMPLETED":
                job_id = job["id"]
            else:
                return CBCManager_pb2.RetrieveSensorsResponse(
                    vmws=[],
                    status="ERROR",
                    message=f"No fue posible generar listado de sensores VM Workload. {e}",
                )

            try:
                r = requests.get(
                    f"{request.cbc_api_host}/jobs/v1/orgs/{org_key}/jobs/{job_id}/download?X-Org={org_key}",
                    timeout=120,
                    verify=False,
                    headers={
                        "X-AUTH-TOKEN": request.auth_token,
                    },
                )
                r.raise_for_status()
            except RequestException as e:
                return CBCManager_pb2.RetrieveSensorsResponse(
                    vmws=[],
                    status="ERROR",
                    message=f"No fue posible solicitar listado de sensores desde consola CBC. {e}",
                )

            sensors = r.text
            sensors = sensors.split("\n")
            sensors_array = []
            test_array = []
            test_json = {}

            for s in sensors:
                s = s.split(",")
                s = [_s.replace('"', "") for _s in s]
                sensors_array.append(s)

            headers = sensors_array[0]
            sensors_array.pop(0)
            sensors_array = [
                saf for saf in sensors_array if len(saf) == len(headers)
            ]  # Limpieza de renglones incompletos (normalmente sólo 1 elemento vacío)

            for sa in sensors_array:
                for i, h in enumerate(headers):
                    test_json[h] = sa[i]
                test_array.append(test_json)
                test_json = {}

            sensors_array = []

            for ta in test_array:
                sensors_array.append(
                    CBCManager_pb2.VMWorkloadSensor(
                        name=ta["name"],
                        loginUserName=ta["loginUserName"],
                        firstName=ta["firstName"],
                        lastName=ta["lastName"],
                        status=ta["status"],
                        registeredTime=ta["registeredTime"],
                        lastInternalIpAddress=ta["lastInternalIpAddress"],
                        lastExternalIpAddress=ta["lastExternalIpAddress"],
                        osVersion=ta["osVersion"],
                        sensorVersion=ta["sensorVersion"],
                        macAddress=ta["macAddress"],
                        sensorOutOfDate=eval(ta["sensorOutOfDate"].capitalize()),
                        deviceId=int(ta["deviceId"]),
                    )
                )

        return CBCManager_pb2.RetrieveSensorsResponse(
            vmws=sensors_array, status="SUCCESS", message=""
        )

    def ServiceManage(self, request, context):
        # Formato fijo, sólo campos de contratación dinámicos
        data = {
            "ADMINISTRACIÓN DE SERVICIO": ["", ""],
            "SERVICIO": [request.service_form.service, ""],
            "MOVIMIENTO": [
                MovementType(request.service_form.movementType)._get_es_name(),
                "",
            ],
            "PRODUCTO": [request.service_form.plan, ""],
            "ORGVDC": [request.service_form.orgVdc, ""],
            "COBRO": [request.service_form.paymentMethod, ""],
            "LICENCIAS": [request.service_form.licenses, ""],
            "CADUCIDAD": [request.service_form.expireDate, ""],
            "ID": [request.service_form.requestQueueId, ""],
            "": ["", ""],
            "*API_HOST": [
                "",
                "URL de acceso a consola de adminsitración de Carbon Black Cloud recibida vía correo electrónico en confirmación de contratación de servicio en Cloud Partner Navigator",
            ],
            "*API_ID": [
                "",
                "Llaves de acceso para interacción vía API. Se obtiene a través de la consola de administración de Carbon Black Cloud: Settings > API Access > API Keys > NAME: SGM-ServiceKey > ACTIONS: API Credentials",
            ],
            "*API_SECRET_KEY": [
                "",
                "",
            ],
            "": [
                "*La información solicitada es de caracter obligatorio y se requiere de acceso con rol de permisos 'Super Admin' a la consola de administración de Carbon Black Cloud para obtenerla",
                "",
            ],
        }

        df = pd.DataFrame(data)

        workbook = Workbook()
        sheet = workbook.active

        # Agregar los datos del DataFrame transpuestos
        for row_num, (index, row) in enumerate(df.T.iterrows(), 1):
            sheet.cell(row=row_num, column=1, value=index)
            last_cell_empty = False

            if "*" in index:
                # Se espera que para algunas celdas se tenga texto partido en varios renglones, por ello se requiere mayor altura
                sheet.row_dimensions[row_num].height = 40
            else:
                # Los demás campos con poco texto tendrán menos altura
                sheet.row_dimensions[row_num].height = 20

            # Dimensiones de columnas fijas
            sheet.column_dimensions["A"].width = 15
            sheet.column_dimensions["B"].width = 25
            sheet.column_dimensions["C"].width = 35

            header_cell = sheet.cell(row=row_num, column=1)

            if type(index) is str and len(index) < 32:
                header_cell.font = Font(size=10, bold=True)
            else:
                header_cell.font = Font(size=8, italic=True)
                header_cell.alignment = Alignment(wrap_text=True)

            if row_num == 1:
                # Titulo centrado en primera columna
                header_cell.alignment = Alignment(
                    horizontal="center", vertical="center"
                )
            else:
                # Titulo centrado en primera columna
                header_cell.alignment = Alignment(vertical="center")

            if index != "" or "*" not in index:
                header_cell.fill = PatternFill(
                    start_color="eeeeee", end_color="eeeeee", fill_type="solid"
                )

            for col_num, value in enumerate(row, 2):
                sheet.cell(row=row_num, column=col_num, value=value)
                current_cell = sheet.cell(row=row_num, column=col_num)

                if col_num > 1:
                    current_cell.border = Border(
                        left=Side(style="thin", color="A9A9A9")
                    )

                if type(value) is str and len(value) > 32:
                    current_cell.font = Font(size=8, italic=True)
                    current_cell.alignment = Alignment(wrap_text=True)

                if last_cell_empty and value == "":
                    sheet.merge_cells(
                        start_row=row_num,
                        start_column=1,
                        end_row=row_num,
                        end_column=col_num,
                    )
                    current_cell.alignment = Alignment(wrap_text=True)
                    continue

                last_cell_empty = value == ""

        # Guardar el archivo de Excel
        filename = f"{data['MOVIMIENTO'][0]}.xlsx"
        workbook.save(filename)

        return BuildRequest(filename, data)

    def SensorManage(self, request, context):
        data = []

        for l in request.licenses:
            data.append(
                {
                    "ORGVDC": [l.orgVdc, ""],
                    "MOVIMIENTO": [
                        CBCManager_pb2.MovementType.keys()[l.movementType],
                        "",
                    ],
                    "MAQUINA_VIRTUAL": [l.hrefVM, ""],
                    "SISTEMA_OPERATIVO": [l.os, ""],
                    "ID": [l.licenseId, ""],
                    "*DEVICE_ID": [
                        "Llaves de acceso para interacción vía API. Se obtiene a través de la consola de administración de Carbon Black Cloud: Settings > API Access > API Keys > NAME: SGM-ServiceKey > ACTIONS: API Credentials",
                        "",
                    ],
                }
            )

        "*La información solicitada es de caracter obligatorio y se requiere de acceso con rol de permisos 'Super Admin' a la consola de administración de Carbon Black Cloud para obtenerla"

        df = pd.DataFrame(data)

        workbook = Workbook()
        # Cabecera/Título
        sheet = workbook.active
        
        header_cell = sheet.cell(row=1, column=1)
        header_cell.value="ADMINISTRACIÓN DE SENSORES"
        header_cell.font = Font(size=10, bold=True)
        # Combinación de 3 celdas para cabeceras
        sheet.merge_cells(
                        start_row=1,
                        start_column=1,
                        end_row=1,
                        end_column=3,
                    )
        # Coloreamos celda de cabecera de color alumnio
        header_cell.fill = PatternFill(
                    start_color="eeeeee", end_color="eeeeee", fill_type="solid"
                )
        # Alineación de texto para cabecera
        header_cell.alignment = Alignment(
                    horizontal="center", vertical="center"
                )
        # Ajuste a altura de renglón
        sheet.row_dimensions[1].height = 20

        # Agregar los datos del DataFrame transpuestos
        for row_num, (index, row) in enumerate(df.T.iterrows(), 1):
            # Cabecera transpuesta
            header_cell = sheet.cell(row=row_num, column=1)

            header_cell.value=index
            sheet.row_dimensions[row_num].height = 20

            header_cell.alignment = Alignment(vertical="center")

            for col_num, value in enumerate(row, 2):
                current_cell = sheet.cell(row=row_num, column=col_num)
                current_cell.value=value

                current_cell.border = Border(
                    left=Side(style="thin", color="A9A9A9")
                )

                if type(value) is str and len(value) > 32:
                    current_cell.font = Font(size=8, italic=True)
                    current_cell.alignment = Alignment(wrap_text=True)

                if last_cell_empty and value == "":
                    sheet.merge_cells(
                        start_row=row_num,
                        start_column=1,
                        end_row=row_num,
                        end_column=col_num,
                    )
                    current_cell.alignment = Alignment(wrap_text=True)
                    continue

                last_cell_empty = value == ""
        # Dimensiones de columnas fijas
        sheet.column_dimensions["A"].width = 15
        sheet.column_dimensions["B"].width = 25
        sheet.column_dimensions["C"].width = 35

        # Guardar el archivo de Excel
        filename = f"{data['MOVIMIENTO'][0]}.xlsx"
        workbook.save(filename)

        return BuildRequest(filename, data)


def BuildRequest(filename, data): # PREGUNTAR A DAVID, SE ESPERA QUE SE PROCESO ALGO CON XLSX O REALMENTE ES EL OBJETO DATA A PROCESAR | SE CAMBIA XLSX POR filename
    # Envío de correo electrónico desde cuenta tipo exchange
    # Configurar las credenciales del remitente
    credentials = Credentials(SENDER_EMAIL, SENDER_PASSWORD)

    account = Account(
        primary_smtp_address=SENDER_EMAIL,
        credentials=credentials,
        autodiscover=True,
        access_type=DELEGATE,
    )

    email = Message(
        account=account,
        subject=f"{data['MOVIMIENTO'][0]}",
        to_recipients=[RECEIVER_EMAIL],
    )

    with open(filename, "rb") as f:
        file_attachment = FileAttachment(name=filename, content=f.read())
    email.attach(file_attachment)

    email.send_and_save()

    return CBCManager_pb2.SensorManagementResponse(
        status="SUCCESS",
        message=f"{data['MOVIMIENTO'][0]}.\nPara cualquier duda puede escribirnos a soporte@cloudtelmex.com o marcar al teléfono 800 026 0000 opción 2",
    )


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    CBCManager_pb2_grpc.add_CBCManagerServiceServicer_to_server(
        CBCManagerService(), server
    )

    with open("server.key", "rb") as fp:
        server_key = fp.read()
    with open("server.pem", "rb") as fp:
        server_cert = fp.read()

    creds = grpc.ssl_server_credentials([(server_key, server_cert)])
    server.add_secure_port("[::]:8446", creds)
    server.start()
    server.wait_for_termination()


if __name__ == "__main__":
    serve()
