import grpc, logging
from typing import Dict, Set, Tuple, Union
from common.Checkers import chk_string
from common.exceptions.ServiceException import ServiceException
from common.database.api.context.Constants import DEFAULT_CONTEXT_ID, DEFAULT_TOPOLOGY_ID

def check_endpoint_id(
    logger : logging.Logger, endpoint_number : int, parent_name : str, endpoint_id : 'EndpointId',
    add_topology_devices_endpoints : Dict[str, Dict[str, Set[str]]],
    default_device_if_empty : Union[str, None] = None,
    predefined_context_id : str = DEFAULT_CONTEXT_ID, acceptable_context_ids : Set[str] = set([DEFAULT_CONTEXT_ID]),
    predefined_topology_id : str = DEFAULT_TOPOLOGY_ID, acceptable_topology_ids : Set[str] = set([DEFAULT_TOPOLOGY_ID])
    ) -> Tuple[str, str, str]:

    try:
        ep_context_id  = chk_string('endpoint_id[#{}].topoId.contextId.contextUuid.uuid'.format(endpoint_number),
                                    endpoint_id.topoId.contextId.contextUuid.uuid,
                                    allow_empty=True)
        ep_topology_id = chk_string('endpoint_id[#{}].topoId.topoId.uuid'.format(endpoint_number),
                                    endpoint_id.topoId.topoId.uuid,
                                    allow_empty=True)
        ep_device_id   = chk_string('endpoint_id[#{}].dev_id.device_id.uuid'.format(endpoint_number),
                                    endpoint_id.dev_id.device_id.uuid,
                                    allow_empty=(default_device_if_empty is not None))
        ep_port_id     = chk_string('endpoint_id[#{}].port_id.uuid'.format(endpoint_number),
                                    endpoint_id.port_id.uuid,
                                    allow_empty=False)
    except Exception as e:
        logger.exception('Invalid arguments:')
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))

    if len(ep_context_id) == 0:
        # Assumption: if no context is specified for an endpoint_id, use parent context
        ep_context_id = predefined_context_id
    elif ep_context_id not in acceptable_context_ids:
        # Assumption: parent and endpoints should belong to the same context
        msg = ' '.join([
            'Context({}) in {} mismatches acceptable Contexts({}).',
            'Optionally, leave field empty to use predefined Context({}).',
        ])
        msg = msg.format(ep_context_id, parent_name, str(acceptable_context_ids), predefined_context_id)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    if len(ep_topology_id) == 0:
        # Assumption: if no topology is specified for an endpoint_id, use parent topology
        ep_topology_id = predefined_topology_id
    elif ep_topology_id not in acceptable_topology_ids:
        msg = ' '.join([
            'Topology({}) in {} mismatches acceptable Topologies({}).',
            'Optionally, leave field empty to use predefined Topology({}).',
        ])
        msg = msg.format(ep_topology_id, parent_name, str(acceptable_topology_ids), predefined_topology_id)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    if (len(ep_device_id) == 0) and (default_device_if_empty is not None):
        ep_device_id = default_device_if_empty

    add_devices = add_topology_devices_endpoints.setdefault(ep_topology_id, dict())
    if ep_device_id in add_devices:
        msg = 'Duplicated Context({})/Topology({})/Device({}) in {}.'
        msg = msg.format(ep_context_id, ep_topology_id, ep_device_id, parent_name)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    add_device_and_endpoints = add_devices.setdefault(ep_device_id, set())

    # Implicit validation: same device cannot appear 2 times in the list of endpoints
    if ep_port_id in add_device_and_endpoints:                                # pragma: no cover
        msg = 'Duplicated Context({})/Topology({})/Device({})/Port({}) in {}.'
        msg = msg.format(ep_context_id, ep_topology_id, ep_device_id, ep_port_id, parent_name)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    add_device_and_endpoints.add(ep_port_id)
    return ep_topology_id, ep_device_id, ep_port_id
