# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 json, logging, re, time
from flask.json import jsonify
from netaddr import IPAddress, IPNetwork
from uuid import uuid4
from common.proto.context_pb2 import (
    ConfigActionEnum, ConfigRule, ConfigRule_Custom, ContextId, Empty, EndPointId,
    QoSProfileId, Service, ServiceId, ServiceStatusEnum, ServiceTypeEnum, Uuid
)
from common.proto.qos_profile_pb2 import (
    QoSProfileValueUnitPair, QoSProfile,QoDConstraintsRequest
)
from common.tools.grpc.ConfigRules import update_config_rule_custom
from common.tools.grpc.Tools import grpc_message_to_json
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Service import json_service_id
from context.client.ContextClient import ContextClient
from qos_profile.client.QoSProfileClient import QoSProfileClient


LOGGER = logging.getLogger(__name__)

ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings'
DEVICE_SETTINGS_KEY = '/device[{:s}]/settings'
RE_CONFIG_RULE_IF_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
MEC_CONSIDERED_FIELDS = [
    'device', 'applicationServer', 'qosProfile', 'sessionId', 'duration',
    'startedAt', 'expiresAt', 'qosStatus'
]

def __init__(self) -> None:
        super().__init__()
        self.qos_profile_client = QoSProfileClient()
        self.client = ContextClient()

def grpc_message_to_qos_table_data(message: QoSProfile) -> dict:
    return {
    'qos_profile_id'            : message.qos_profile_id.qos_profile_id.uuid,
    'name'                      : message.name,
    'description'               : message.description,
    'status'                    : message.status,
    'targetMinUpstreamRate'     : grpc_message_to_json(message.targetMinUpstreamRate),
    'maxUpstreamRate'           : grpc_message_to_json(message.maxUpstreamRate),
    'maxUpstreamBurstRate'      : grpc_message_to_json(message.maxUpstreamBurstRate),
    'targetMinDownstreamRate'   : grpc_message_to_json(message.targetMinDownstreamRate),
    'maxDownstreamRate'         : grpc_message_to_json(message.maxDownstreamRate),
    'maxDownstreamBurstRate'    : grpc_message_to_json(message.maxDownstreamBurstRate),
    'minDuration'               : grpc_message_to_json(message.minDuration),
    'maxDuration'               : grpc_message_to_json(message.maxDuration),
    'priority'                  : message.priority,
    'packetDelayBudget'         : grpc_message_to_json(message.packetDelayBudget),
    'jitter'                    : grpc_message_to_json(message.jitter),
    'packetErrorLossRate'       : message.packetErrorLossRate,
    }

def create_qos_profile_from_json(qos_profile_data: dict) -> QoSProfile:
    def create_QoSProfileValueUnitPair(data) -> QoSProfileValueUnitPair:
        return QoSProfileValueUnitPair(value=data['value'], unit=data['unit'])
    qos_profile = QoSProfile()
    qos_profile.qos_profile_id.CopyFrom(QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_data['qos_profile_id'])))
    qos_profile.name = qos_profile_data['name']
    qos_profile.description = qos_profile_data['description']
    qos_profile.status = qos_profile_data['status']
    qos_profile.targetMinUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinUpstreamRate']))
    qos_profile.maxUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamRate']))
    qos_profile.maxUpstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamBurstRate']))
    qos_profile.targetMinDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinDownstreamRate']))
    qos_profile.maxDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamRate']))
    qos_profile.maxDownstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamBurstRate']))
    qos_profile.minDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['minDuration']))
    qos_profile.maxDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDuration']))
    qos_profile.priority = qos_profile_data['priority']
    qos_profile.packetDelayBudget.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['packetDelayBudget']))
    qos_profile.jitter.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['jitter']))
    qos_profile.packetErrorLossRate = qos_profile_data['packetErrorLossRate']
    return qos_profile

def ip_withoutsubnet(ip_withsubnet,neededip):
    network = IPNetwork(ip_withsubnet)
    return IPAddress(neededip) in network


def QOD_2_service(client,qod_info: dict,qos_profile_id: int,duration: int) -> Service:
    qos_profile_client = QoSProfileClient()
    service = Service()
    service_config_rules = service.service_config.config_rules

    request_cr_key = '/request'
    request_cr_value = {k: qod_info[k] for k in MEC_CONSIDERED_FIELDS if k in qod_info}  
    config_rule = ConfigRule()
    config_rule.action = ConfigActionEnum.CONFIGACTION_SET
    config_rule_custom = ConfigRule_Custom()
    config_rule_custom.resource_key = request_cr_key
    config_rule_custom.resource_value = json.dumps(request_cr_value)
    config_rule.custom.CopyFrom(config_rule_custom)
    service_config_rules.append(config_rule)

    if 'device' in qod_info and 'applicationServer' in qod_info:
        a_ip = qod_info['device'].get('ipv4Address')
        z_ip = qod_info['applicationServer'].get('ipv4Address')

        LOGGER.debug('a_ip = {:s}'.format(str(a_ip)))
        LOGGER.debug('z_ip = {:s}'.format(str(z_ip)))

        if a_ip and z_ip:
            devices = client.ListDevices(Empty()).devices
            #LOGGER.info('devices = {:s}'.format(str(devices)))

            ip_interface_name_dict = {}
            
            for device in devices:
                #LOGGER.info('device..uuid = {:s}'.format(str(device.device_id.device_uuid.uuid)))
                #LOGGER.info('device..name = {:s}'.format(str(device.name)))
                device_endpoint_uuids = {ep.name: ep.endpoint_id.endpoint_uuid.uuid for ep in device.device_endpoints}
                #LOGGER.info('device_endpoint_uuids = {:s}'.format(str(device_endpoint_uuids)))

                for cr in device.device_config.config_rules:
                    if cr.WhichOneof('config_rule') != 'custom':
                        continue
                    #LOGGER.info('cr = {:s}'.format(str(cr)))
                    match_subif = RE_CONFIG_RULE_IF_SUBIF.match(cr.custom.resource_key)
                    if not match_subif:
                        continue
                    address_ip =json.loads(cr.custom.resource_value).get('address_ip')
                    LOGGER.debug('cr..address_ip = {:s}'.format(str(address_ip)))
                    short_port_name = match_subif.groups()[0]
                    LOGGER.debug('short_port_name = {:s}'.format(str(short_port_name)))

                    ip_interface_name_dict[address_ip] = short_port_name
                    
                    if not (ip_withoutsubnet(a_ip, address_ip) or ip_withoutsubnet(z_ip, address_ip)):
                        continue
                    ep_id = EndPointId()
                    ep_id.endpoint_uuid.uuid = device_endpoint_uuids.get(short_port_name , '')
                    ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid
                    service.service_endpoint_ids.append(ep_id)
                    LOGGER.debug(f"the ip address{ep_id}")
    
            #LOGGER.info('ip_interface_name_dict = {:s}'.format(str(ip_interface_name_dict)))
    
    settings_cr_key = '/settings'
    settings_cr_value = {}
    update_config_rule_custom(service_config_rules, settings_cr_key, settings_cr_value) 
    service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
    service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
    qod_info["sessionID"]=str(uuid4())
    qod_info["context"]='admin'
    service.service_id.service_uuid.uuid = qod_info['sessionID']
    service.service_id.context_id.context_uuid.uuid = qod_info["context"]
    service.name = qod_info.get('qos_profile_id', qos_profile_id)    
    current_time = time.time() 
    duration_days = duration  # days as i saw it in the proto files
    id = QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_id))
    request = QoDConstraintsRequest(qos_profile_id=id,start_timestamp=current_time,duration=duration_days) #defined attributes in proto file for the QoDconstraint rquest message 
    qos_profiles_constraint = qos_profile_client.GetConstraintListFromQoSProfile(request)
    LOGGER.info(f'current time contains{current_time}')
    LOGGER.info(f'current duration time contains{duration_days}')
    LOGGER.info(f'id : {id}')
    for cs in qos_profiles_constraint:
        if cs.WhichOneof('constraint') == 'schedule' and cs.WhichOneof('constraint') == 'qos_profile': #the method of which one of 
            cs.schedule.start_timestamp = current_time
            cs.schedule.duration_days = duration_days
            cs.qos_profile.qos_profile_id=id
            cs.qos_profile.qos_profile_name.CopyFrom(qos_profile_client.name) #i copied this from the qosprofile
            LOGGER.info(f'the cs : {cs}')
        service.service_constraints.append(cs)

    return service



def service_2_qod(service: Service) -> dict:
    response = {}
    for config_rule in service.service_config.config_rules:
        resource_value_json = json.loads(config_rule.custom.resource_value)
        LOGGER.info(f"the resource value contains:{resource_value_json}")
        if config_rule.custom.resource_key != '/request':
            continue
        if 'device' in resource_value_json and 'ipv4Address' in resource_value_json['device']:
            response['device'] = {
                'ipv4Address': resource_value_json['device']['ipv4Address']
            }
        
        if 'applicationServer' in resource_value_json and 'ipv4Address' in resource_value_json['applicationServer']:
            response['applicationServer'] = {
                'ipv4Address': resource_value_json['applicationServer']['ipv4Address']
            }
        
    if service.name:
            response['qos_profile_id'] = service.name
        
    if service.service_id:
        response['sessionId'] = service.service_id.service_uuid.uuid
    if service.service_constraints:
        for constraint in service.service_constraints:
            if constraint.WhichOneof('constraint') == 'schedule':
                response['duration'] = float(constraint.schedule.duration_days* (86400))
                LOGGER.info(f'the duration in seconds: {response["duration"]}')
                response['startedAt'] = int(constraint.schedule.start_timestamp)
                response['expiresAt'] = response['startedAt'] + response['duration']


    return response


def format_grpc_to_json(grpc_reply):
    return jsonify(grpc_message_to_json(grpc_reply))

def grpc_context_id(context_uuid):
    return ContextId(**json_context_id(context_uuid))

def grpc_service_id(context_uuid, service_uuid):
    return ServiceId(**json_service_id(service_uuid, context_id=json_context_id(context_uuid)))
