# 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 json, logging, re
from typing import Dict, Tuple
from flask import request
from flask.json import jsonify
from flask_restful import Resource
#from common.tools.context_queries.Slice import get_slice_by_uuid
import pyangbind.lib.pybindJSON as pybindJSON
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.proto.context_pb2 import ContextId
from common.tools.context_queries.Topology import get_topology_details
from common.tools.object_factory.Context import json_context_id
from context.client.ContextClient import ContextClient
from nbi.service.rest_server.nbi_plugins.ietf_network.Constants import CLIENT_ID, PROVIDER_ID
from nbi.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
from nbi.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_OK, HTTP_SERVERERROR
from .bindings import ietf_network

LOGGER = logging.getLogger(__name__)

ADMIN_CONTEXT_UUIDS  = {DEFAULT_CONTEXT_NAME}
ADMIN_TOPOLOGY_UUIDS = {DEFAULT_TOPOLOGY_NAME}

class Networks(Resource):
    @HTTP_AUTH.login_required
    def get(self):
        LOGGER.info('Request: {:s}'.format(str(request)))
        topology_id = ''
        try:
            context_client = ContextClient()
            #target = get_slice_by_uuid(context_client, vpn_id, rw_copy=True)
            #if target is None:
            #    raise Exception('VPN({:s}) not found in database'.format(str(vpn_id)))

            ietf_nets = ietf_network()

            topology_ids = context_client.ListTopologyIds(ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)))
            for topology_id in topology_ids.topology_ids:
                context_uuid  = topology_id.context_id.context_uuid.uuid
                topology_uuid = topology_id.topology_uuid.uuid
                if context_uuid in ADMIN_CONTEXT_UUIDS and topology_uuid in ADMIN_TOPOLOGY_UUIDS: continue

                topology_details = get_topology_details(
                    context_client, topology_uuid, context_uuid=context_uuid, rw_copy=True
                )
                if topology_details is None:
                    MSG = 'Topology({:s}/{:s}) not found'
                    LOGGER.warning(MSG.format(context_uuid, topology_uuid))
                    continue

                topology_name = topology_details.name
                ietf_net = ietf_nets.networks.network.add(topology_name)
                ietf_net.te.name = 'Huawei-Network'

                match = re.match(r'providerId\-([^\-]*)-clientId-([^\-]*)-topologyId-([^\-]*)', topology_name)
                if match is not None:
                    provider_id, client_id, topology_id = match.groups()
                    ietf_net.te_topology_identifier.provider_id = int(provider_id)
                    ietf_net.te_topology_identifier.client_id   = int(client_id)
                    ietf_net.te_topology_identifier.topology_id = str(topology_id)

                ietf_net.network_types.te_topology._set_present()
                # TODO: resolve setting of otn_topology/eth_tran_topology network type; not working in bindings.
                # See below.
                # ietf_net.network_types.te_topology.otn_topology._set_present()
                # ietf_net.network_types.te_topology.eth_tran_topology._set_present()

                device_uuid_to_name   : Dict[str,             str] = dict()
                endpoint_uuid_to_name : Dict[Tuple[str, str], str] = dict()
                for device in topology_details.devices:
                    device_uuid = device.device_id.device_uuid.uuid
                    device_name = device.name
                    device_uuid_to_name[device_uuid] = device_name
                    device_uuid_to_name[device_name] = device_name
                    ietf_node = ietf_net.node.add(device_name)
                    ietf_node.te_node_id = device_name

                    for endpoint in device.device_endpoints:
                        endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid
                        endpoint_name = endpoint.name
                        if endpoint_name == 'mgmt': continue
                        endpoint_uuid_to_name[(device_uuid, endpoint_uuid)] = endpoint_name
                        endpoint_uuid_to_name[(device_name, endpoint_uuid)] = endpoint_name
                        endpoint_uuid_to_name[(device_uuid, endpoint_name)] = endpoint_name
                        endpoint_uuid_to_name[(device_name, endpoint_name)] = endpoint_name
                        ietf_node.termination_point.add(endpoint_name)

                for link in topology_details.links:
                    link_name = link.name
                    ietf_link = ietf_net.link.add(link_name)
                    #ietf_link.link_id = link_name
                    
                    src_dev_uuid = link.link_endpoint_ids[ 0].device_id.device_uuid.uuid
                    src_ep_uuid  = link.link_endpoint_ids[ 0].endpoint_uuid.uuid
                    ietf_link.source.source_node = device_uuid_to_name.get(
                        src_dev_uuid, src_dev_uuid
                    )
                    ietf_link.source.source_tp   = endpoint_uuid_to_name.get(
                        (src_dev_uuid, src_ep_uuid), src_ep_uuid
                    )

                    dst_dev_uuid = link.link_endpoint_ids[-1].device_id.device_uuid.uuid
                    dst_ep_uuid  = link.link_endpoint_ids[-1].endpoint_uuid.uuid
                    ietf_link.destination.dest_node = device_uuid_to_name.get(
                        dst_dev_uuid, dst_dev_uuid
                    )
                    ietf_link.destination.dest_tp   = endpoint_uuid_to_name.get(
                        (dst_dev_uuid, dst_ep_uuid), dst_ep_uuid
                    )

            # TODO: improve these workarounds to enhance performance
            json_response = json.loads(pybindJSON.dumps(ietf_nets, mode='ietf'))

            # TODO: workaround to set network types manually. Currently does not work; refine bindings.
            # Seems limitation of pyangbind using multiple augmentations and nested empty containers.
            for json_network in json_response['ietf-network:networks']['network']:
                net_te_topo_id = json_network.get('ietf-te-topology:te-topology-identifier', {}).get('topology-id')
                if net_te_topo_id is None: continue
                net_te_topo_type = json_network['network-types']['ietf-te-topology:te-topology']
                if net_te_topo_id == '1': net_te_topo_type['ietf-otn-topology:otn-topology'] = {}
                if net_te_topo_id == '2': net_te_topo_type['ietf-eth-te-topology:eth-tran-topology'] = {}

            response = jsonify(json_response)
            response.status_code = HTTP_OK
        except Exception as e: # pylint: disable=broad-except
            LOGGER.exception('Something went wrong Retrieving Topology({:s})'.format(str(topology_id)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response
