import grpc, logging
from prometheus_client import Counter, Histogram
from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID
from common.exceptions.ServiceException import ServiceException
from context.proto.context_pb2 import Empty, Link, LinkId, Topology
from context.proto.context_pb2_grpc import ContextServiceServicer
from context.service.database.api.Database import Database
from .Tools import check_link_id_request, check_link_request

LOGGER = logging.getLogger(__name__)

GETTOPOLOGY_COUNTER_STARTED    = Counter  ('context_gettopology_counter_started',
                                          'Context:GetTopology counter of requests started'  )
GETTOPOLOGY_COUNTER_COMPLETED  = Counter  ('context_gettopology_counter_completed',
                                          'Context:GetTopology counter of requests completed')
GETTOPOLOGY_COUNTER_FAILED     = Counter  ('context_gettopology_counter_failed',
                                          'Context:GetTopology counter of requests failed'   )
GETTOPOLOGY_HISTOGRAM_DURATION = Histogram('context_gettopology_histogram_duration',
                                          'Context:GetTopology histogram of request duration')

ADDLINK_COUNTER_STARTED    = Counter  ('context_addlink_counter_started',
                                       'Context:AddLink counter of requests started'  )
ADDLINK_COUNTER_COMPLETED  = Counter  ('context_addlink_counter_completed',
                                       'Context:AddLink counter of requests completed')
ADDLINK_COUNTER_FAILED     = Counter  ('context_addlink_counter_failed',
                                       'Context:AddLink counter of requests failed'   )
ADDLINK_HISTOGRAM_DURATION = Histogram('context_addlink_histogram_duration',
                                       'Context:AddLink histogram of request duration')

DELETELINK_COUNTER_STARTED    = Counter  ('context_deletelink_counter_started',
                                          'Context:DeleteLink counter of requests started'  )
DELETELINK_COUNTER_COMPLETED  = Counter  ('context_deletelink_counter_completed',
                                          'Context:DeleteLink counter of requests completed')
DELETELINK_COUNTER_FAILED     = Counter  ('context_deletelink_counter_failed',
                                          'Context:DeleteLink counter of requests failed'   )
DELETELINK_HISTOGRAM_DURATION = Histogram('context_deletelink_histogram_duration',
                                          'Context:DeleteLink histogram of request duration')

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

    @GETTOPOLOGY_HISTOGRAM_DURATION.time()
    def GetTopology(self, request : Empty, grpc_context : grpc.ServicerContext) -> Topology:
        GETTOPOLOGY_COUNTER_STARTED.inc()
        try:
            LOGGER.debug('GetTopology request: {}'.format(str(request)))

            # ----- Validate request data and pre-conditions -----------------------------------------------------------
            db_context = self.database.context(DEFAULT_CONTEXT_UUID).create()
            db_topology = db_context.topology(DEFAULT_TOPOLOGY_UUID).create()

            # ----- Retrieve data from the database --------------------------------------------------------------------
            json_topology = db_topology.dump()

            # ----- Compose reply --------------------------------------------------------------------------------------
            reply = Topology(**json_topology)
            LOGGER.debug('GetTopology reply: {}'.format(str(reply)))
            GETTOPOLOGY_COUNTER_COMPLETED.inc()
            return reply
        except ServiceException as e:                               # pragma: no cover (ServiceException not thrown)
            LOGGER.exception('GetTopology exception')
            GETTOPOLOGY_COUNTER_FAILED.inc()
            grpc_context.abort(e.code, e.details)
        except Exception as e:                                      # pragma: no cover
            LOGGER.exception('GetTopology exception')
            GETTOPOLOGY_COUNTER_FAILED.inc()
            grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))

    @ADDLINK_HISTOGRAM_DURATION.time()
    def AddLink(self, request : Link, grpc_context : grpc.ServicerContext) -> LinkId:
        ADDLINK_COUNTER_STARTED.inc()
        try:
            LOGGER.debug('AddLink request: {}'.format(str(request)))

            # ----- Validate request data and pre-conditions -----------------------------------------------------------
            link_id, db_endpoints = check_link_request('AddLink', request, self.database, LOGGER)

            # ----- Implement changes in the database ------------------------------------------------------------------
            db_context = self.database.context(DEFAULT_CONTEXT_UUID).create()
            db_topology = db_context.topology(DEFAULT_TOPOLOGY_UUID).create()
            db_link = db_topology.link(link_id).create()
            for db_endpoint in db_endpoints:
                link_endpoint_id = '{}/{}'.format(
                    db_endpoint.device_uuid, db_endpoint.endpoint_uuid)
                db_link.endpoint(link_endpoint_id).create(db_endpoint)

            # ----- Compose reply --------------------------------------------------------------------------------------
            reply = LinkId(**db_link.dump_id())
            LOGGER.debug('AddLink reply: {}'.format(str(reply)))
            ADDLINK_COUNTER_COMPLETED.inc()
            return reply
        except ServiceException as e:
            LOGGER.exception('AddLink exception')
            ADDLINK_COUNTER_FAILED.inc()
            grpc_context.abort(e.code, e.details)
        except Exception as e:                                      # pragma: no cover
            LOGGER.exception('AddLink exception')
            ADDLINK_COUNTER_FAILED.inc()
            grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))

    @DELETELINK_HISTOGRAM_DURATION.time()
    def DeleteLink(self, request : LinkId, grpc_context : grpc.ServicerContext) -> Empty:
        DELETELINK_COUNTER_STARTED.inc()
        try:
            LOGGER.debug('DeleteLink request: {}'.format(str(request)))

            # ----- Validate request data and pre-conditions -----------------------------------------------------------
            link_id = check_link_id_request('DeleteLink', request, self.database, LOGGER)

            # ----- Implement changes in the database ------------------------------------------------------------------
            db_context = self.database.context(DEFAULT_CONTEXT_UUID).create()
            db_topology = db_context.topology(DEFAULT_TOPOLOGY_UUID).create()
            db_topology.link(link_id).delete()

            # ----- Compose reply --------------------------------------------------------------------------------------
            reply = Empty()
            LOGGER.debug('DeleteLink reply: {}'.format(str(reply)))
            DELETELINK_COUNTER_COMPLETED.inc()
            return reply
        except ServiceException as e:
            LOGGER.exception('DeleteLink exception')
            DELETELINK_COUNTER_FAILED.inc()
            grpc_context.abort(e.code, e.details)
        except Exception as e:                                      # pragma: no cover
            LOGGER.exception('DeleteLink exception')
            DELETELINK_COUNTER_FAILED.inc()
            grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))
