# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import grpc, logging, os, time
from typing import Dict
from prometheus_client import Histogram
from common.Constants import ServiceNameEnum
from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, get_env_var_name
from common.method_wrappers.Decorator import MetricTypeEnum, MetricsPool, safe_and_metered_rpc_method
from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException
from common.proto.context_pb2 import (
    Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link,MyConfig,MyConfigId ,MyConfig,MyConfigList)
from common.proto.device_pb2 import MonitoringSettings
from common.proto.device_pb2_grpc import DeviceServiceServicer
from common.tools.context_queries.Device import get_device
from common.tools.mutex_queues.MutexQueues import MutexQueues
from context.client.ContextClient import ContextClient
from .driver_api._Driver import _Driver
from .driver_api.DriverInstanceCache import DriverInstanceCache, get_driver
from .monitoring.MonitoringLoops import MonitoringLoops
from .drivers.oc_driver.OCDriver import OCDriver
from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI
from .Tools import extract_resources
from .Tools import (
    check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules,
    get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints,
    populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints)

LOGGER = logging.getLogger(__name__)

METRICS_POOL = MetricsPool('Device', 'RPC')

METRICS_POOL_DETAILS = MetricsPool('Device', 'execution', labels={
    'driver': '', 'operation': '', 'step': '',
})

class OpenConfigServicer(DeviceServiceServicer):
    def __init__(self, driver_instance_cache : DriverInstanceCache, monitoring_loops : MonitoringLoops) -> None:
        LOGGER.debug('Creating Servicer...')
        self.driver_instance_cache = driver_instance_cache
        self.monitoring_loops = monitoring_loops
        self.mutex_queues = MutexQueues()
        LOGGER.debug('Servicer Created')
    
    @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
    def AddOpenConfigDevice(self, request : MyConfig, context : grpc.ServicerContext) -> DeviceId:
           
            device_uuid = request.device_id.device_uuid.uuid
            device_type=request.device_type
            ocdriver=OCDriver()
            connection_config_rules = check_connect_rules(request.device_config)
            check_no_endpoints(request.device_endpoints)

            context_client = ContextClient()
            device = get_device(context_client, device_uuid, rw_copy=True)
            if device is None:
                # not in context, create blank one to get UUID, and populate it below
                device = Device()
                device.device_id.CopyFrom(request.device_id)            # pylint: disable=no-member
                device.name = request.name
                device.device_type = request.device_type
                device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED
                device.device_drivers.extend(request.device_drivers)    # pylint: disable=no-member
                device.device_config.CopyFrom(request.device_config)    # pylint: disable=no-member
                device_id = context_client.SetDevice(device)
                device = get_device(context_client, device_id.device_uuid.uuid, rw_copy=True)

            # update device_uuid to honor UUID provided by Context
            device_uuid = device.device_id.device_uuid.uuid
            LOGGER.debug('device type %s',device)
            t2 = time.time()
            
            self.mutex_queues.wait_my_turn(device_uuid)
            t3 = time.time()
            #temp fix to solve the error
            #todo check what to pass here
            resources_to_get = []
            try:
                #driver : _Driver = get_driver(self.driver_instance_cache, device)
                results_getconfig=ocdriver.GetConfig(resource_keys=resources_to_get,device_uuid=device_uuid)
                #results_getconfig = driver.GetConfig(resources_to_get,device_uuid)
            except Exception as error :
                LOGGER.debug("error %s",error)    
                
    # @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
    # def ConfigureOpenConfigDevice(self, request : MyConfig, context : grpc.ServicerContext) -> DeviceId:
               
    #     device_id = request.myconfig_id
    #     device_uuid = device_id.myconfig_uuid
   
    #     self.mutex_queues.wait_my_turn(device_uuid)
    #     t1 = time.time()
    #     try:
            
    #         context_client = ContextClient()
    #         t2 = time.time()
    #         device = get_device(
    #             context_client, device_uuid, rw_copy=True, include_endpoints=False, include_components=False,
    #             include_config_rules=True)
    #         if device is None:
    #             raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')

    #         t3 = time.time()
    #         device_controller_uuid = get_device_controller_uuid(device)
    #         if device_controller_uuid is not None:
    #             device = get_device(
    #                 context_client, device_controller_uuid, rw_copy=True, include_endpoints=False,
    #                 include_components=False, include_config_rules=True)
    #             if device is None:
    #                 raise NotFoundException(
    #                     'Device', device_controller_uuid, extra_details='loading in ConfigureDevice')

    #         device_uuid = device.device_id.device_uuid.uuid
    #         driver : _Driver = get_driver(self.driver_instance_cache, device)
    #         resources_to_set, resources_to_delete = compute_rules_to_add_delete(device, request)
    #         results_setconfig = driver.SetConfig(resources=resources_to_set)
    #     except Exception as e:
    #             LOGGER.debug("error in updating %s",e) 
       
    '''
    @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
    def ConfigureOpticalDevice (self, request : MyConfig, context:grpc.ServicerContext) -> Empty:
        LOGGER.info('Updating from ConfigureOpticalDevice %s',request)
        device_id = request.myconfig_id
        device_uuid = device_id.myconfig_uuid
        resources=[]
        add_proccess=False
        update_interface=False
        result=None
        config =eval(request.config)
       
       
        filter_fields= ["frequency","target-output-power","interface","operational-mode"]
        try:
            
            context_client = ContextClient()
           
            device = get_device(
                context_client, device_uuid, rw_copy=True, include_endpoints=False, include_components=False,
                include_config_rules=True)
           
            if device is None:
                LOGGER.debug("device is none")
                raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
            resources,conditions=extract_resources(config=config,device=device)
            driver : _Driver = get_driver(self.driver_instance_cache, device)
            LOGGER.info("resource  %s conditions %s",resources,conditions)
       
            result = driver.SetConfig(resources=resources,conditions=conditions)
            driver.GetConfig(resource_keys=filter_fields)
            
        except Exception as e:
                LOGGER.info("error in configuring %s",e)    

        
        LOGGER.info("result %s",result)
        return Empty()
    '''
 
    #modified Andrea
    @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
    def ConfigureOpticalDevice (self, request : MyConfig, context:grpc.ServicerContext) -> Empty:
        LOGGER.info('Setting from ConfigureOpticalDevice with Flows %s',request)
        #device_id = request.myconfig_id
        #device_uuid = device_id.myconfig_uuid
        device_uuid = request.myconfig_id.myconfig_uuid
        LOGGER.info("AAAAAAAAAAAAAAAAAAAAAAA device uuid {}".format(device_uuid))
        resources=[]
        result=None

        config =eval(request.config)
       
       
        filter_fields= ["frequency","target-output-power","interface","operational-mode"]
        try:
            
            context_client = ContextClient()
           
            device = get_device(
                context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False,
                include_config_rules=False)
           
            if device is None:
                LOGGER.info("AAAAAAAAAAAAAAAAAAAAAAAdevice is none")
                raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
            resources,conditions=extract_resources(config=config,device=device)
            driver : _Driver = get_driver(self.driver_instance_cache, device)
            LOGGER.info("resource  %s conditions %s",resources,conditions)
       
            result = driver.SetConfig(resources=resources,conditions=conditions)
            #todo
            #add a control with the NETCONF get
            #driver.GetConfig(resource_keys=filter_fields)
            
        except Exception as e:
                LOGGER.info("error in configuring %s",e)    

        
        LOGGER.info("result %s",result)
        return Empty()
 