import grpc, logging, operator
from typing import Iterator
#from common.exceptions.ServiceException import ServiceException
from common.metrics.Metrics import create_metrics, safe_and_metered_rpc_method
from common.orm.Database import Database
from common.orm.backend.Tools import key_to_str
from context.proto.context_pb2 import (
    Context, ContextEvent, ContextId, ContextIdList, ContextList, Device, DeviceEvent, DeviceId, DeviceIdList,
    DeviceList, DeviceOperationalStatusEnum, Empty, Link, LinkEvent, LinkId, LinkIdList, LinkList, Service,
    ServiceEvent, ServiceId, ServiceIdList, ServiceList, Topology, TopologyEvent, TopologyId, TopologyIdList,
    TopologyList)
from context.proto.context_pb2_grpc import ContextServiceServicer
from context.service.database.ConfigModel import (
    ConfigModel, ConfigRuleModel, grpc_to_enum__config_action)
from context.service.database.ConstraintModel import ConstraintModel, ConstraintsModel
from context.service.database.ContextModel import ContextModel
from context.service.database.DeviceModel import (
    DeviceModel, DriverModel, grpc_to_enum__device_driver, grpc_to_enum__device_operational_status)
from context.service.database.EndPointModel import EndPointModel
from context.service.database.LinkModel import LinkModel
from context.service.database.ServiceModel import (
    ServiceModel, grpc_to_enum__service_status, grpc_to_enum__service_type)
from context.service.database.Tools import fast_hasher
from context.service.database.TopologyModel import TopologyModel
from context.service.database.RelationModels import (
    LinkEndPointModel, ServiceEndPointModel, TopologyDeviceModel, TopologyLinkModel)

#from .Tools import check_link_id_request, check_link_request

LOGGER = logging.getLogger(__name__)

SERVICE_NAME = 'Context'
METHOD_NAMES = [
    'ListContextIds',  'ListContexts',   'GetContext',  'SetContext',  'RemoveContext',  'GetContextEvents',
    'ListTopologyIds', 'ListTopologies', 'GetTopology', 'SetTopology', 'RemoveTopology', 'GetTopologyEvents',
    'ListDeviceIds',   'ListDevices',    'GetDevice',   'SetDevice',   'RemoveDevice',   'GetDeviceEvents',
    'ListLinkIds',     'ListLinks',      'GetLink',     'SetLink',     'RemoveLink',     'GetLinkEvents',
    'ListServiceIds',  'ListServices',   'GetService',  'SetService',  'RemoveService',  'GetServiceEvents',
]
METRICS = create_metrics(SERVICE_NAME, METHOD_NAMES)

class ContextServiceServicerImpl(ContextServiceServicer):
    def __init__(self, database : Database):
        LOGGER.debug('Creating Servicer...')
        self.database = database
        LOGGER.debug('Servicer Created')

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListContextIds(self, request: Empty, context : grpc.ServicerContext) -> ContextIdList:
        db_context_pks = sorted(list(ContextModel.get_primary_keys(self.database)))
        return ContextIdList(context_ids=[ContextModel(self.database, pk).dump_id() for pk in db_context_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListContexts(self, request: Empty, context : grpc.ServicerContext) -> ContextList:
        db_context_pks = sorted(list(ContextModel.get_primary_keys(self.database)))
        return ContextList(contexts=[ContextModel(self.database, pk).dump() for pk in db_context_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetContext(self, request: ContextId, context : grpc.ServicerContext) -> Context:
        str_context_key = key_to_str(request.context_uuid.uuid)
        db_context = ContextModel(self.database, str_context_key, auto_load=False)
        found = db_context.load()
        if not found: return Context()
        return Context(**db_context.dump(
            include_services=True, include_topologies=True))

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def SetContext(self, request: Context, context : grpc.ServicerContext) -> ContextId:
        str_context_key = key_to_str(request.context_id.context_uuid.uuid)
        db_context = ContextModel(self.database, str_context_key)
        db_context.context_uuid = request.context_id.context_uuid.uuid
        db_context.save()

        for i,topology_id in enumerate(request.topology_ids):
            if topology_id.context_id.context_uuid.uuid != db_context.context_uuid:
                msg = '{:s}({:s}) != {:s}({:s})'
                raise ValueError(msg.format(
                    'request.context_id.context_uuid.uuid',
                    request.context_id.context_uuid.uuid,
                    'request.topology_ids[{:d}].context_id.context_uuid.uuid'.format(i),
                    topology_id.context_id.context_uuid.uuid))
            str_topology_key = key_to_str([str_context_key, topology_id.topology_uuid.uuid])
            db_topology = TopologyModel(self.database, str_topology_key)
            db_topology.context_fk = db_context
            db_topology.topology_uuid = topology_id.topology_uuid.uuid
            db_topology.save()

        for i,service_id in enumerate(request.service_ids):
            if service_id.context_id.context_uuid.uuid != db_context.context_uuid:
                msg = '{:s}({:s}) != {:s}({:s})'
                raise ValueError(msg.format(
                    'request.context_id.context_uuid.uuid',
                    request.context_id.context_uuid.uuid,
                    'request.service_ids[{:d}].context_id.context_uuid.uuid'.format(i),
                    service_id.context_id.context_uuid.uuid))
            str_service_key = key_to_str([str_context_key, service_id.service_uuid.uuid])
            db_service = ServiceModel(self.database, str_service_key)
            db_service.context_fk = db_context
            db_service.service_uuid = service_id.service_uuid.uuid
            db_service.save()

        return ContextId(**db_context.dump_id())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def RemoveContext(self, request: ContextId, context : grpc.ServicerContext) -> Empty:
        str_context_key = key_to_str(request.context_uuid.uuid)
        db_context = ContextModel(self.database, str_context_key)
        db_context.delete()
        return Empty()

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetContextEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[ContextEvent]:
        pass

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListTopologyIds(self, request: ContextId, context : grpc.ServicerContext) -> TopologyIdList:
        db_context = ContextModel(self.database, request.context_uuid.uuid)
        db_topology_pks = db_context.references(TopologyModel)
        db_topology_pks = sorted(map(operator.itemgetter(0), db_topology_pks))
        return TopologyIdList(topology_ids=[TopologyModel(self.database, pk).dump_id() for pk in db_topology_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListTopologies(self, request: ContextId, context : grpc.ServicerContext) -> TopologyList:
        db_context = ContextModel(self.database, request.context_uuid.uuid)
        db_topology_pks = db_context.references(TopologyModel)
        db_topology_pks = sorted(map(operator.itemgetter(0), db_topology_pks))
        return TopologyList(topologies=[TopologyModel(self.database, pk).dump() for pk in db_topology_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetTopology(self, request: TopologyId, context : grpc.ServicerContext) -> Topology:
        str_topology_key = key_to_str([request.context_id.context_uuid.uuid, request.topology_uuid.uuid])
        db_topology = TopologyModel(self.database, str_topology_key, auto_load=False)
        found = db_topology.load()
        if not found: return Topology()
        return Topology(**db_topology.dump(
            include_devices=True, include_links=True))

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def SetTopology(self, request: Topology, context : grpc.ServicerContext) -> TopologyId:
        str_context_key = key_to_str([request.topology_id.context_id.context_uuid.uuid])
        str_topology_key = key_to_str([str_context_key, request.topology_id.topology_uuid.uuid])
        db_context = ContextModel(self.database, str_context_key)
        db_topology = TopologyModel(self.database, str_topology_key)
        db_topology.context_fk = db_context
        db_topology.topology_uuid = request.topology_id.topology_uuid.uuid
        db_topology.save()

        for device_id in request.device_ids:
            str_device_key = key_to_str(device_id.device_uuid.uuid)
            db_device = DeviceModel(self.database, str_device_key)
            db_device.device_uuid = device_id.device_uuid.uuid
            db_device.save()

            str_topology_device_key = key_to_str([str_topology_key, str_device_key], separator='--')
            db_topology_device = TopologyDeviceModel(self.database, str_topology_device_key)
            db_topology_device.topology_fk = db_topology
            db_topology_device.device_fk = db_device
            db_topology_device.save()

        for link_id in request.link_ids:
            str_link_key = key_to_str(link_id.link_uuid.uuid)
            db_link = LinkModel(self.database, str_link_key)
            db_link.link_uuid = link_id.link_uuid.uuid
            db_link.save()

            str_topology_link_key = key_to_str([str_topology_key, str_link_key], separator='--')
            db_topology_link = TopologyLinkModel(self.database, str_topology_link_key)
            db_topology_link.topology_fk = db_topology
            db_topology_link.link_fk = db_link
            db_topology_link.save()

        return TopologyId(**db_topology.dump_id())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def RemoveTopology(self, request: TopologyId, context : grpc.ServicerContext) -> Empty:
        str_topology_key = key_to_str([request.context_id.context_uuid.uuid, request.topology_uuid.uuid])
        db_topology = TopologyModel(self.database, str_topology_key)
        db_topology.delete()
        return Empty()

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetTopologyEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[TopologyEvent]:
        pass

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListDeviceIds(self, request: Empty, context : grpc.ServicerContext) -> DeviceIdList:
        db_device_pks = sorted(list(DeviceModel.get_primary_keys(self.database)))
        return DeviceIdList(device_ids=[DeviceModel(self.database, pk).dump_id() for pk in db_device_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListDevices(self, request: Empty, context : grpc.ServicerContext) -> DeviceList:
        db_device_pks = sorted(list(DeviceModel.get_primary_keys(self.database)))
        return DeviceList(devices=[DeviceModel(self.database, pk).dump() for pk in db_device_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetDevice(self, request: DeviceId, context : grpc.ServicerContext) -> Device:
        str_device_key = key_to_str(request.device_uuid.uuid)
        db_device = DeviceModel(self.database, str_device_key, auto_load=False)
        found = db_device.load()
        if not found: return Device()
        return Device(**db_device.dump(
            include_config_rules=True, include_drivers=True, include_endpoints=True))

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def SetDevice(self, request: Device, context : grpc.ServicerContext) -> DeviceId:
        device_uuid = request.device_id.device_uuid.uuid
        str_device_key = key_to_str(device_uuid)

        str_config_key = key_to_str([str_device_key, 'running'], separator=':')
        db_running_config = ConfigModel(self.database, str_config_key)
        db_running_config.save()

        db_device = DeviceModel(self.database, str_device_key)
        db_device.device_uuid = device_uuid
        db_device.device_type = request.device_type
        db_device.device_operational_status = grpc_to_enum__device_operational_status(request.device_operational_status)
        db_device.device_config_fk = db_running_config
        db_device.save()

        for i,config_rule in enumerate(request.device_config.config_rules):
            str_rule_key_hash = fast_hasher(config_rule.resource_key)
            str_device_config_rule_key = key_to_str([str_config_key, str_rule_key_hash], separator=':')
            db_device_config_rule = ConfigRuleModel(self.database, str_device_config_rule_key)
            db_device_config_rule.config_fk = db_running_config
            db_device_config_rule.position = i
            db_device_config_rule.action = grpc_to_enum__config_action(config_rule.action)
            db_device_config_rule.key = config_rule.resource_key
            db_device_config_rule.value = config_rule.resource_value
            db_device_config_rule.save()

        for i,driver in enumerate(request.device_drivers):
            orm_driver = grpc_to_enum__device_driver(driver)
            str_device_driver_key = key_to_str([str_device_key, orm_driver.name])
            db_device_driver = DriverModel(self.database, str_device_driver_key)
            db_device_driver.device_fk = db_device
            db_device_driver.driver = orm_driver
            db_device_driver.save()

        for i,endpoint in enumerate(request.device_endpoints):
            endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid
            endpoint_device_uuid = endpoint.endpoint_id.device_id.device_uuid.uuid
            if len(endpoint_device_uuid) == 0: endpoint_device_uuid = device_uuid
            if device_uuid != endpoint_device_uuid:
                msg = '{:s}({:s}) != {:s}({:s})'
                raise ValueError(msg.format(
                    'request.device_id.device_uuid.uuid',
                    device_uuid,
                    'request.device_endpoints[{:d}].device_id.device_uuid.uuid'.format(i),
                    endpoint_device_uuid))

            db_topology = None
            endpoint_topology_context_uuid = endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid
            endpoint_topology_uuid = endpoint.endpoint_id.topology_id.topology_uuid.uuid
            if len(endpoint_topology_context_uuid) > 0 and len(endpoint_topology_uuid) > 0:
                str_context_key = key_to_str([endpoint_topology_context_uuid])
                db_context = ContextModel(self.database, str_context_key)
                db_context.context_uuid = endpoint_topology_context_uuid
                db_context.save()

                str_topology_key = key_to_str([endpoint_topology_context_uuid, endpoint_topology_uuid])
                db_topology = TopologyModel(self.database, str_topology_key)
                db_topology.context_fk = db_context
                db_topology.topology_uuid = endpoint_topology_uuid
                db_topology.save()

                str_topology_device_key = key_to_str([str_topology_key, str_device_key], separator='--')
                db_topology_device = TopologyDeviceModel(self.database, str_topology_device_key)
                db_topology_device.topology_fk = db_topology
                db_topology_device.device_fk = db_device
                db_topology_device.save()

            str_device_endpoint_key = key_to_str([str_device_key, endpoint_uuid])
            if db_topology is not None:
                str_device_endpoint_key = key_to_str([str_device_endpoint_key, str_topology_key], separator=':')
            db_device_endpoint = EndPointModel(self.database, str_device_endpoint_key)
            db_device_endpoint.device_fk = db_device
            db_device_endpoint.endpoint_uuid = endpoint_uuid
            db_device_endpoint.endpoint_type = endpoint.endpoint_type
            if db_topology is not None: db_device_endpoint.topology_fk = db_topology
            db_device_endpoint.save()

        return DeviceId(**db_device.dump_id())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def RemoveDevice(self, request: DeviceId, context : grpc.ServicerContext) -> Empty:
        str_device_key = key_to_str(request.device_uuid.uuid)
        db_device = DeviceModel(self.database, str_device_key)

        for db_endpoint_pk,_ in db_device.references(EndPointModel):
            EndPointModel(self.database, db_endpoint_pk).delete()

        for db_topology_device_pk,_ in db_device.references(TopologyDeviceModel):
            TopologyDeviceModel(self.database, db_topology_device_pk).delete()

        for db_driver_pk,_ in db_device.references(DriverModel):
            DriverModel(self.database, db_driver_pk).delete()

        db_config = ConfigModel(self.database, db_device.device_config_fk)
        for db_config_rule_pk,_ in db_config.references(ConfigRuleModel):
            ConfigRuleModel(self.database, db_config_rule_pk).delete()

        db_device.delete()
        db_config.delete()

        return Empty()

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetDeviceEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]:
        pass

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListLinkIds(self, request: Empty, context : grpc.ServicerContext) -> LinkIdList:
        db_link_pks = sorted(list(LinkModel.get_primary_keys(self.database)))
        return LinkIdList(link_ids=[LinkModel(self.database, pk).dump_id() for pk in db_link_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListLinks(self, request: Empty, context : grpc.ServicerContext) -> LinkList:
        db_link_pks = sorted(list(LinkModel.get_primary_keys(self.database)))
        return LinkList(links=[LinkModel(self.database, pk).dump() for pk in db_link_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetLink(self, request: LinkId, context : grpc.ServicerContext) -> Link:
        str_link_key = key_to_str(request.link_uuid.uuid)
        db_link = LinkModel(self.database, str_link_key, auto_load=False)
        found = db_link.load()
        if not found: return Link()
        return Link(**db_link.dump())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def SetLink(self, request: Link, context : grpc.ServicerContext) -> LinkId:
        str_link_key = key_to_str(request.link_id.link_uuid.uuid)
        db_link = LinkModel(self.database, str_link_key)
        db_link.link_uuid = request.link_id.link_uuid.uuid
        db_link.save()

        for endpoint_id in request.link_endpoint_ids:
            endpoint_uuid = endpoint_id.endpoint_uuid.uuid
            endpoint_device_uuid = endpoint_id.device_id.device_uuid.uuid

            str_device_key = key_to_str(endpoint_device_uuid)
            str_config_key = key_to_str([str_device_key, 'running'], separator=':')
            db_running_config = ConfigModel(self.database, str_config_key)
            db_running_config.save()

            db_device = DeviceModel(self.database, str_device_key)
            db_device.device_uuid = endpoint_device_uuid
            db_device.device_operational_status = grpc_to_enum__device_operational_status(
                DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED)
            db_device.device_config_fk = db_running_config
            db_device.save()

            db_topology = None
            endpoint_topology_context_uuid = endpoint_id.topology_id.context_id.context_uuid.uuid
            endpoint_topology_uuid = endpoint_id.topology_id.topology_uuid.uuid
            if len(endpoint_topology_context_uuid) > 0 and len(endpoint_topology_uuid) > 0:
                str_context_key = key_to_str([endpoint_topology_context_uuid])
                db_context = ContextModel(self.database, str_context_key)
                db_context.context_uuid = endpoint_topology_context_uuid
                db_context.save()

                str_topology_key = key_to_str([endpoint_topology_context_uuid, endpoint_topology_uuid])
                db_topology = TopologyModel(self.database, str_topology_key)
                db_topology.context_fk = db_context
                db_topology.topology_uuid = endpoint_topology_uuid
                db_topology.save()

                str_topology_device_key = key_to_str([str_topology_key, str_device_key], separator='--')
                db_topology_device = TopologyDeviceModel(self.database, str_topology_device_key)
                db_topology_device.topology_fk = db_topology
                db_topology_device.device_fk = db_device
                db_topology_device.save()

            str_device_endpoint_key = key_to_str([str_device_key, endpoint_uuid])
            if db_topology is not None:
                str_device_endpoint_key = key_to_str([str_device_endpoint_key, str_topology_key], separator=':')
            db_device_endpoint = EndPointModel(self.database, str_device_endpoint_key)
            db_device_endpoint.device_fk = db_device
            db_device_endpoint.endpoint_uuid = endpoint_uuid
            if db_topology is not None: db_device_endpoint.topology_fk = db_topology
            db_device_endpoint.save()

            str_link_endpoint_key = key_to_str([str_link_key, str_device_endpoint_key], separator='--')
            db_link_endpoint = LinkEndPointModel(self.database, str_link_endpoint_key)
            db_link_endpoint.link_fk = db_link
            db_link_endpoint.endpoint_fk = db_device_endpoint
            db_link_endpoint.save()

        return LinkId(**db_link.dump_id())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def RemoveLink(self, request: LinkId, context : grpc.ServicerContext) -> Empty:
        str_link_key = key_to_str(request.link_uuid.uuid)
        db_link = LinkModel(self.database, str_link_key)

        for db_link_endpoint_pk,_ in db_link.references(LinkEndPointModel):
            LinkEndPointModel(self.database, db_link_endpoint_pk).delete()

        for db_topology_link_pk,_ in db_link.references(TopologyLinkModel):
            TopologyLinkModel(self.database, db_topology_link_pk).delete()

        db_link.delete()
        return Empty()

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetLinkEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[LinkEvent]:
        pass

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListServiceIds(self, request: ContextId, context : grpc.ServicerContext) -> ServiceIdList:
        db_context = ContextModel(self.database, request.context_uuid.uuid)
        db_service_pks = db_context.references(ServiceModel)
        db_service_pks = sorted(map(operator.itemgetter(0), db_service_pks))
        return ServiceIdList(service_ids=[ServiceModel(self.database, pk).dump_id() for pk in db_service_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def ListServices(self, request: ContextId, context : grpc.ServicerContext) -> ServiceList:
        db_context = ContextModel(self.database, request.context_uuid.uuid)
        db_service_pks = db_context.references(ServiceModel)
        db_service_pks = sorted(map(operator.itemgetter(0), db_service_pks))
        return ServiceList(services=[ServiceModel(self.database, pk).dump() for pk in db_service_pks])

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetService(self, request: ServiceId, context : grpc.ServicerContext) -> Service:
        str_service_key = key_to_str([request.context_id.context_uuid.uuid, request.service_uuid.uuid])
        db_service = ServiceModel(self.database, str_service_key, auto_load=False)
        found = db_service.load()
        if not found: return Service()
        return Service(**db_service.dump(
            include_endpoint_ids=True, include_constraints=True, include_config_rules=True))

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def SetService(self, request: Service, context : grpc.ServicerContext) -> ServiceId:
        context_uuid = request.service_id.context_id.context_uuid.uuid
        str_context_key = key_to_str([context_uuid])
        str_service_key = key_to_str([str_context_key, request.service_id.service_uuid.uuid])

        str_service_config_key = key_to_str([str_service_key, 'running'], separator=':')
        db_service_running_config = ConfigModel(self.database, str_service_config_key)
        db_service_running_config.save()

        db_constraints = ConstraintsModel(self.database, str_service_key)
        db_constraints.save()

        db_context = ContextModel(self.database, str_context_key)
        db_service = ServiceModel(self.database, str_service_key)
        db_service.context_fk = db_context
        db_service.service_uuid = request.service_id.service_uuid.uuid
        db_service.service_type = grpc_to_enum__service_type(request.service_type)
        db_service.service_constraints_fk = db_constraints
        db_service.service_status = grpc_to_enum__service_status(request.service_status.service_status)
        db_service.service_config_fk = db_service_running_config
        db_service.save()

        for i,endpoint_id in enumerate(request.service_endpoint_ids):
            endpoint_uuid = endpoint_id.endpoint_uuid.uuid
            endpoint_device_uuid = endpoint_id.device_id.device_uuid.uuid

            str_device_key = key_to_str(endpoint_device_uuid)
            str_device_config_key = key_to_str([str_device_key, 'running'], separator=':')
            db_device_running_config = ConfigModel(self.database, str_device_config_key)
            db_device_running_config.save()

            db_device = DeviceModel(self.database, str_device_key)
            db_device.device_uuid = endpoint_device_uuid
            db_device.device_operational_status = grpc_to_enum__device_operational_status(
                DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED)
            db_device.device_config_fk = db_device_running_config
            db_device.save()

            db_topology = None
            endpoint_topology_context_uuid = endpoint_id.topology_id.context_id.context_uuid.uuid
            if len(endpoint_topology_context_uuid) > 0 and context_uuid != endpoint_topology_context_uuid:
                msg = '{:s}({:s}) != {:s}({:s})'
                raise ValueError(msg.format(
                    'request.service_id.context_id.context_uuid.uuid',
                    context_uuid,
                    'request.service_endpoint_ids[{:d}].topology_id.context_id.context_uuid.uuid'.format(i),
                    endpoint_topology_context_uuid))

            endpoint_topology_uuid = endpoint_id.topology_id.topology_uuid.uuid
            if len(endpoint_topology_context_uuid) > 0 and len(endpoint_topology_uuid) > 0:
                str_context_key = key_to_str([endpoint_topology_context_uuid])
                db_context = ContextModel(self.database, str_context_key)
                db_context.context_uuid = endpoint_topology_context_uuid
                db_context.save()

                str_topology_key = key_to_str([endpoint_topology_context_uuid, endpoint_topology_uuid])
                db_topology = TopologyModel(self.database, str_topology_key)
                db_topology.context_fk = db_context
                db_topology.topology_uuid = endpoint_topology_uuid
                db_topology.save()

                str_topology_device_key = key_to_str([str_topology_key, str_device_key], separator='--')
                db_topology_device = TopologyDeviceModel(self.database, str_topology_device_key)
                db_topology_device.topology_fk = db_topology
                db_topology_device.device_fk = db_device
                db_topology_device.save()

            str_device_endpoint_key = key_to_str([str_device_key, endpoint_uuid])
            if db_topology is not None:
                str_device_endpoint_key = key_to_str([str_device_endpoint_key, str_topology_key], separator=':')
            db_device_endpoint = EndPointModel(self.database, str_device_endpoint_key)
            db_device_endpoint.device_fk = db_device
            db_device_endpoint.endpoint_uuid = endpoint_uuid
            if db_topology is not None: db_device_endpoint.topology_fk = db_topology
            db_device_endpoint.save()

            str_service_endpoint_key = key_to_str([str_service_key, str_device_endpoint_key], separator='--')
            db_service_endpoint = ServiceEndPointModel(self.database, str_service_endpoint_key)
            db_service_endpoint.service_fk = db_service
            db_service_endpoint.endpoint_fk = db_device_endpoint
            db_service_endpoint.save()

        for i,constraint in enumerate(request.service_constraints):
            str_rule_key_hash = fast_hasher(constraint.constraint_type)
            str_service_constraint_key = key_to_str([str_service_key, str_rule_key_hash], separator=':')
            db_service_constraint = ConstraintModel(self.database, str_service_constraint_key)
            db_service_constraint.constraints_fk = db_constraints
            db_service_constraint.position = i
            db_service_constraint.constraint_type = constraint.constraint_type
            db_service_constraint.constraint_value = constraint.constraint_value
            db_service_constraint.save()

        for i,config_rule in enumerate(request.service_config.config_rules):
            str_rule_key_hash = fast_hasher(config_rule.resource_key)
            str_service_config_rule_key = key_to_str([str_service_config_key, str_rule_key_hash], separator=':')
            db_service_config_rule = ConfigRuleModel(self.database, str_service_config_rule_key)
            db_service_config_rule.config_fk = db_service_running_config
            db_service_config_rule.position = i
            db_service_config_rule.action = grpc_to_enum__config_action(config_rule.action)
            db_service_config_rule.key = config_rule.resource_key
            db_service_config_rule.value = config_rule.resource_value
            db_service_config_rule.save()

        return ServiceId(**db_service.dump_id())

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def RemoveService(self, request: ServiceId, context : grpc.ServicerContext) -> Empty:
        str_service_key = key_to_str([request.context_id.context_uuid.uuid, request.service_uuid.uuid])
        db_service = ServiceModel(self.database, str_service_key)

        for db_service_endpoint_pk,_ in db_service.references(ServiceEndPointModel):
            ServiceEndPointModel(self.database, db_service_endpoint_pk).delete()

        db_config = ConfigModel(self.database, db_service.service_config_fk)
        for db_config_rule_pk,_ in db_config.references(ConfigRuleModel):
            ConfigRuleModel(self.database, db_config_rule_pk).delete()

        db_constraints = ConstraintsModel(self.database, db_service.service_constraints_fk)
        for db_constraint_pk,_ in db_constraints.references(ConstraintModel):
            ConstraintModel(self.database, db_constraint_pk).delete()

        db_service.delete()
        db_config.delete()
        db_constraints.delete()
        return Empty()

    @safe_and_metered_rpc_method(METRICS, LOGGER)
    def GetServiceEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]:
        pass
