# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.

"""
P4 service handler for the TeraFlow SDN controller.
"""

import json
import logging
from typing import Any, List, Optional, Tuple, Union
from common.orm.Database import Database
from common.orm.HighLevel import get_object
from common.orm.backend.Tools import key_to_str
# from common.proto.context_pb2 import Device
# from common.tools.object_factory.ConfigRule import json_config_rule_delete,\
#     json_config_rule_set
from common.type_checkers.Checkers import chk_type
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from service.service.database.ConfigModel import get_config_rules
from service.service.database.ContextModel import ContextModel
# from service.service.database.DeviceModel import DeviceModel
from service.service.database.ServiceModel import ServiceModel
from service.service.service_handler_api._ServiceHandler import _ServiceHandler

LOGGER = logging.getLogger(__name__)


class P4ServiceHandler(_ServiceHandler):
    """
    P4ServiceHandler class inherits the abstract _ServiceHandler class
    to provision network connectivity upon P4 devices.

    Attributes
    ----------
    db_service: ServiceModel
        The service instance from the local in-memory database.
    database: Database
        The instance of the local in-memory database.
    context_client: ContextClient
        gRPC client to get Context services
    device_client : DeviceClient
        gRPC client to get Device services
    settings: map
        Extra settings required by the service handler.
    """
    def __init__(self,  # pylint: disable=super-init-not-called
                 db_service: ServiceModel,
                 database: Database,
                 context_client: ContextClient,
                 device_client : DeviceClient,
                 **settings) -> None:
        self.__db_service = db_service
        self.__database = database
        self.__context_client = context_client
        self.__device_client = device_client

        self.__db_context: ContextModel = get_object(
            self.__database, ContextModel, self.__db_service.context_fk)
        str_service_key = key_to_str(
            [self.__db_context.context_uuid, self.__db_service.service_uuid])
        db_config = get_config_rules(
            self.__database, str_service_key, "running")
        self.__resolver = None
        self.__config = None

    def SetEndpoint(self, endpoints: List[Tuple[str, str, Optional[str]]]) \
            -> List[Union[bool, Exception]]:
        """
        Create/Update service endpoints.

        :param endpoints: list of tuples, each containing a device_uuid,
        endpoint_uuid and, optionally, the topology_uuid of the endpoint to add.
        :return: list of results for endpoint changes requested.
        Return values must be in the same order as the requested endpoints.
        If an endpoint is properly added, True must be returned;
        otherwise, the Exception that is raised during processing
        must be returned.
        """
        chk_type("endpoints", endpoints, list)
        if len(endpoints) == 0:
            return []
        return []

    def DeleteEndpoint(self, endpoints: List[Tuple[str, str, Optional[str]]]) \
            -> List[Union[bool, Exception]]:
        """
        Delete service endpoints.

        :param endpoints: list of tuples, each containing a device_uuid,
        endpoint_uuid and, optionally, the topology_uuid of the endpoint to
        delete.
        :return: list of results for endpoint deletions requested.
        Return values must be in the same order as the requested endpoints.
        If an endpoint is properly deleted, True must be returned;
        otherwise, the Exception that is raised during processing
        must be returned.
        """
        chk_type("endpoints", endpoints, list)
        if len(endpoints) == 0:
            return []
        return []

    def SetConstraint(self, constraints: List[Tuple[str, Any]]) \
            -> List[Union[bool, Exception]]:
        """
        Create/Update service constraints.

        :param constraints: List of tuples, each containing a constraint_type
        and the new constraint_value to be set.
        :return: List of results for constraint changes requested.
        Return values must be in the same order as the requested constraints.
        If a constraint is properly set, True must be returned;
        otherwise, the Exception that is raised during the processing
        must be returned.
        """
        chk_type("constraints", constraints, list)
        if len(constraints) == 0:
            return []

        msg = f"SetConstraint() not yet implemented by the" \
              f" P4 service handler. \nThe following constraints " \
              f"are being ignored: {str(constraints)}"
        LOGGER.warning(msg)
        return [Exception(msg) for _ in range(len(constraints))]

    def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \
            -> List[Union[bool, Exception]]:
        """
        Delete service constraints.

        :param constraints: List of tuples, each containing a constraint_type
        pointing to the constraint to be deleted, and a constraint_value
        containing possible additionally required values to locate the
        constraint to be removed.
        :return: List of results for constraint deletions requested.
        Return values must be in the same order as the requested constraints.
        If a constraint is properly deleted, True must be returned;
        otherwise, the Exception that is raised during the processing
        must be returned.
        """
        chk_type("constraints", constraints, list)
        if len(constraints) == 0:
            return []

        msg = f"DeleteConstraint() not yet implemented by the" \
              f" P4 service handler. \nThe following constraints " \
              f"are being ignored: {str(constraints)}"
        LOGGER.warning(msg)
        return [Exception(msg) for _ in range(len(constraints))]

    def SetConfig(self, resources: List[Tuple[str, Any]]) \
            -> List[Union[bool, Exception]]:
        """
        Create/Update configuration for a list of service resources.

        :param resources: List of tuples, each containing a resource_key
        pointing to the resource to be modified, and a resource_value
        containing the new value to be set.
        :return: List of results for resource key changes requested.
        Return values must be in the same order as the requested resource keys.
        If a resource is properly set, True must be returned;
        otherwise, the Exception that is raised during the processing
        must be returned.
        """
        chk_type("resources", resources, list)
        if len(resources) == 0:
            return []

        results = []
        for resource in resources:
            try:
                resource_key, resource_value = resource
                resource_value = json.loads(resource_value)
                # Do the job
                # results.append(True)
            except Exception as ex:  # pylint: disable=broad-except
                LOGGER.exception(
                    "Failed to execute SetConfig(%s)", str(resource))
                results.append(ex)

        return results

    def DeleteConfig(self, resources: List[Tuple[str, Any]]) \
            -> List[Union[bool, Exception]]:
        """
        Delete configuration for a list of service resources.

        :param resources: List of tuples, each containing a resource_key
        pointing to the resource to be modified, and a resource_value containing
        possible additionally required values to locate the value to be removed.
        :return: List of results for resource key deletions requested.
        Return values must be in the same order as the requested resource keys.
        If a resource is properly deleted, True must be returned;
        otherwise, the Exception that is raised during the processing
        must be returned.
        """
        chk_type("resources", resources, list)
        if len(resources) == 0:
            return []

        results = []
        for resource in resources:
            try:
                resource_key, _ = resource
                # Do the job
                # results.append(True)
            except Exception as ex:  # pylint: disable=broad-except
                LOGGER.exception(
                    "Failed to execute DeleteConfig(%s)", str(resource))
                results.append(ex)

        return results
