# 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
import libyang, logging, os
from typing import Any
from common.proto.context_pb2 import TopologyDetails, Device, Link
from .NameMapping import NameMappings
from context.client.ContextClient import ContextClient
from common.tools.object_factory.Device import json_device_id
from common.proto.context_pb2 import DeviceId

LOGGER = logging.getLogger(__name__)

YANG_DIR = os.path.join(os.path.dirname(__file__), 'yang')
YANG_MODULES = ['ietf-network', 'ietf-network-topology', 'ietf-l3-unicast-topology', 'ietf-sap-ntw', 'ietf-vpn-common']

class YangHandler:
    def __init__(self) -> None:
        self._yang_context = libyang.Context(YANG_DIR)
        for yang_module_name in YANG_MODULES:
            LOGGER.info('Loading module: {:s}'.format(str(yang_module_name)))
            self._yang_context.load_module(yang_module_name).feature_enable_all()



    def compose_network(self, te_topology_name: str, topology_details: TopologyDetails) -> dict:
        networks = self._yang_context.create_data_path('/ietf-network:networks')


        network = networks.create_path(f'network[network-id="{te_topology_name}"]')
        network.create_path('network-id', te_topology_name)

        network_types = network.create_path('network-types')
        sap_network = network_types.create_path('ietf-sap-ntw:sap-network')


        available_services = ["ietf-vpn-common:l3vpn"] #no se muy bien como coger todos los servicios disponibles
        
        for service in available_services:
            sap_network.create_path('service-type', service)  #no se muy bien como coger todos los servicios disponibles

        connected_tps = set()  # Conjunto local para TPs conectados
        name_mappings = NameMappings()

        # Depuración inicial
        LOGGER.info(f"Procesando topology_details: {len(topology_details.devices)} devices, {len(topology_details.links)} links.")



        if topology_details.links:
            for link in topology_details.links:
                source_name = name_mappings.get_endpoint_name(link.link_endpoint_ids[0])
                dest_name = name_mappings.get_endpoint_name(link.link_endpoint_ids[-1])
                connected_tps.add(source_name)
                connected_tps.add(dest_name)


        if topology_details.devices:
            for device in topology_details.devices:
                self.compose_node(device, name_mappings, network, connected_tps)




        return json.loads(networks.print_mem('json'))


        
    def compose_node(self, dev: Device, name_mappings: NameMappings, network: Any, connected_tps: set) -> None:      

        device_name = dev.name
        name_mappings.store_device_name(dev)

        node = network.create_path(f'node[node-id="{device_name}"]')
        node.create_path('node-id', device_name)

    

        context_client = ContextClient()
        device = context_client.GetDevice(DeviceId(**json_device_id(device_name)))

        for endpoint in device.device_endpoints:
            name_mappings.store_endpoint_name(dev, endpoint)
            #LOGGER.info(f"Métodos disponibles en 'endpoint': {dir(endpoint)}")

        

        self._process_device_config(device, node, connected_tps)


   
    
    def _process_device_config(self, device: Device, node: Any, connected_tps: set) -> None:
        #sLOGGER.info(f"Métodos disponibles en 'node': {dir(node)}")
        #LOGGER.info(f"Métodos disponibles en 'nedevice': {dir(device)}")
        sap_device = ""
        for config in device.device_config.config_rules:
            
            if config.WhichOneof('config_rule') == 'custom' and config.custom.resource_key == '_connect/settings':
                try:
                    settings_data = json.loads(config.custom.resource_value)
                    sap_device = settings_data.get('sap_id', "")
                except ValueError:
                    sap_device = "ERROR"
                
                break
                
        for config in device.device_config.config_rules:

            if config.WhichOneof('config_rule') != 'custom' or '/interface[' not in config.custom.resource_key:
                continue
            
            for endpoint in device.device_endpoints:
                endpoint_name = endpoint.name
                if endpoint.endpoint_id.endpoint_uuid.uuid in connected_tps:
                    continue
                if f'/interface[{endpoint_name}]' in config.custom.resource_key or f'/interface[{endpoint_name}.' in config.custom.resource_key:
                    self._create_termination_point(node, sap_device, config.custom.resource_value)

    
    
    def _create_termination_point(self, node: Any, sap_device: str, resource_value: str) -> None:
        config_data = json.loads(resource_value)
        ip_addresses = self._extract_ip_addresses(config_data)
        if ip_addresses:
            #tp = node.create_path(f'ietf-network-topology:termination-point[tp-id="{interface_name}"]')
            service = node.create_path(f'ietf-sap-ntw:service[service-type="ietf-vpn-common:l3vpn"]')

            sap_id_value = config_data.get('sap_id', "ERROR")
            final_sap_id = f"SAP{sap_device}-{sap_id_value}"

            sap = service.create_path(f'sap[sap-id="{final_sap_id}"]')
            #sap.create_path('peer-sap-id', interface_name)
            sap.create_path('peer-sap-id', "NOT IMPLEMENTED")

            sap_status = sap.create_path('sap-status')
            sap_status.create_path('status', "ietf-vpn-common:op-up") #NOT IMPLEMENTED

            service_status = sap.create_path('service-status')
            admin_status = service_status.create_path('admin-status')
            oper_status = service_status.create_path('oper-status')
            admin_status.create_path('status', "ietf-vpn-common:admin-up") #NOT IMPLEMENTED
            oper_status.create_path('status', "ietf-vpn-common:op-up") #NOT IMPLEMENTED



    @staticmethod

    def _extract_ip_addresses(resource_value: dict) -> list:
        ip_addresses = []
        if 'address_ip' in resource_value:
            ip_addresses.append(resource_value['address_ip'])
        if 'address_ipv6' in resource_value:
            ip_addresses.append(resource_value['address_ipv6'])
        return ip_addresses


    def destroy(self) -> None:
        self._yang_context.destroy()
