# 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 logging
from typing import Dict, List
from flask.json import jsonify
from flask_restful import Resource, request
from werkzeug.exceptions import BadRequest
from common.proto.context_pb2 import Empty, LinkTypeEnum
from common.tools.grpc.Tools import grpc_message_to_json
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
from vnt_manager.client.VNTManagerClient import VNTManagerClient

from .Tools import (
    format_grpc_to_json, grpc_connection_id, grpc_context, grpc_context_id, grpc_device,
    grpc_device_id, grpc_link, grpc_link_id, grpc_policy_rule_id,
    grpc_service_id, grpc_service, grpc_slice, grpc_slice_id, grpc_topology, grpc_topology_id
)

LOGGER = logging.getLogger(__name__)


class _Resource(Resource):
    def __init__(self) -> None:
        super().__init__()
        self.context_client = ContextClient()
        self.device_client  = DeviceClient()
        self.service_client = ServiceClient()
        self.vntmanager_client = VNTManagerClient()
        self.slice_client   = SliceClient()

class ContextIds(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListContextIds(Empty()))

class Contexts(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListContexts(Empty()))

    def post(self):
        json_requests = request.get_json()
        if 'contexts' in json_requests:
            json_requests = json_requests['contexts']
        return jsonify([
            grpc_message_to_json(self.context_client.SetContext(grpc_context(context)))
            for context in json_requests
        ])

class Context(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.GetContext(grpc_context_id(context_uuid)))

    def put(self, context_uuid : str):
        context = request.get_json()
        if context_uuid != context['context_id']['context_uuid']['uuid']:
            raise BadRequest('Mismatching context_uuid')
        return format_grpc_to_json(self.context_client.SetContext(grpc_context(context)))

    def delete(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.RemoveContext(grpc_context_id(context_uuid)))

class DummyContexts(_Resource):
    def get(self):
        contexts = grpc_message_to_json(
            self.context_client.ListContexts(Empty()),
            use_integers_for_enums=True
        )['contexts']
        devices = grpc_message_to_json(
            self.context_client.ListDevices(Empty()),
            use_integers_for_enums=True
        )['devices']
        links = grpc_message_to_json(
            self.context_client.ListLinks(Empty()),
            use_integers_for_enums=True
        )['links']

        topologies  = list()
        slices      = list()
        services    = list()
        connections = list()

        for context in contexts:
            context_uuid = context['context_id']['context_uuid']['uuid']
            context_id = grpc_context_id(context_uuid)

            topologies.extend(grpc_message_to_json(
                self.context_client.ListTopologies(context_id),
                use_integers_for_enums=True
            )['topologies'])

            slices.extend(grpc_message_to_json(
                self.context_client.ListSlices(context_id),
                use_integers_for_enums=True
            )['slices'])

            context_services = grpc_message_to_json(
                self.context_client.ListServices(context_id),
                use_integers_for_enums=True
            )['services']
            services.extend(context_services)

            for service in context_services:
                service_uuid = service['service_id']['service_uuid']['uuid']
                service_id = grpc_service_id(context_uuid, service_uuid)
                connections.extend(grpc_message_to_json(
                    self.context_client.ListConnections(service_id),
                    use_integers_for_enums=True
                )['connections'])

        for device in devices:
            for config_rule in device['device_config']['config_rules']:
                if 'custom' not in config_rule: continue
                resource_value = config_rule['custom']['resource_value']
                if not isinstance(resource_value, str): continue
                try:
                    resource_value = json.loads(resource_value)
                except: # pylint: disable=bare-except
                    pass
                config_rule['custom']['resource_value'] = resource_value

        dummy_context = {'dummy_mode': True}
        if len(contexts   ) > 0: dummy_context['contexts'   ] = contexts
        if len(topologies ) > 0: dummy_context['topologies' ] = topologies
        if len(devices    ) > 0: dummy_context['devices'    ] = devices
        if len(links      ) > 0: dummy_context['links'      ] = links
        if len(slices     ) > 0: dummy_context['slices'     ] = slices
        if len(services   ) > 0: dummy_context['services'   ] = services
        if len(connections) > 0: dummy_context['connections'] = connections
        return jsonify(dummy_context)

class TopologyIds(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListTopologyIds(grpc_context_id(context_uuid)))

class Topologies(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListTopologies(grpc_context_id(context_uuid)))

    def post(self, context_uuid : str):
        json_requests = request.get_json()
        if 'topologies' in json_requests:
            json_requests = json_requests['topologies']
        for topology in json_requests:
            if context_uuid != topology['topology_id']['context_id']['context_uuid']['uuid']:
                raise BadRequest('Mismatching context_uuid')
        return jsonify([
            grpc_message_to_json(self.context_client.SetTopology(grpc_topology(topology)))
            for topology in json_requests
        ])

class Topology(_Resource):
    def get(self, context_uuid : str, topology_uuid : str):
        return format_grpc_to_json(self.context_client.GetTopology(grpc_topology_id(context_uuid, topology_uuid)))

    def put(self, context_uuid : str, topology_uuid : str):
        topology = request.get_json()
        if context_uuid != topology['topology_id']['context_id']['context_uuid']['uuid']:
            raise BadRequest('Mismatching context_uuid')
        if topology_uuid != topology['topology_id']['topology_uuid']['uuid']:
            raise BadRequest('Mismatching topology_uuid')
        return format_grpc_to_json(self.context_client.SetTopology(grpc_topology(topology)))

    def delete(self, context_uuid : str, topology_uuid : str):
        return format_grpc_to_json(self.context_client.RemoveTopology(grpc_topology_id(context_uuid, topology_uuid)))

class TopologyDetails(_Resource):
    def get(self, context_uuid : str, topology_uuid : str):
        topology_id = grpc_topology_id(context_uuid, topology_uuid)
        return format_grpc_to_json(self.context_client.GetTopologyDetails(topology_id))

class ServiceIds(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListServiceIds(grpc_context_id(context_uuid)))

class Services(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListServices(grpc_context_id(context_uuid)))

    def post(self, context_uuid : str):
        json_requests = request.get_json()
        if 'services' in json_requests:
            json_requests = json_requests['services']
        if isinstance(json_requests, dict):
            json_requests = [json_requests]
        for service in json_requests:
            if context_uuid != service['service_id']['context_id']['context_uuid']['uuid']:
                raise BadRequest('Mismatching context_uuid')
        return jsonify([
            grpc_message_to_json(self.service_client.CreateService(grpc_service(service)))
            for service in json_requests
        ])

class Service(_Resource):
    def get(self, context_uuid : str, service_uuid : str):
        return format_grpc_to_json(self.context_client.GetService(grpc_service_id(context_uuid, service_uuid)))

    def put(self, context_uuid : str, service_uuid : str):
        service = request.get_json()
        if context_uuid != service['service_id']['context_id']['context_uuid']['uuid']:
            raise BadRequest('Mismatching context_uuid')
        if service_uuid != service['service_id']['service_uuid']['uuid']:
            raise BadRequest('Mismatching service_uuid')
        return format_grpc_to_json(self.service_client.UpdateService(grpc_service(service)))

    def delete(self, context_uuid : str, service_uuid : str):
        return format_grpc_to_json(self.service_client.DeleteService(grpc_service_id(context_uuid, service_uuid)))

class SliceIds(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListSliceIds(grpc_context_id(context_uuid)))

class Slices(_Resource):
    def get(self, context_uuid : str):
        return format_grpc_to_json(self.context_client.ListSlices(grpc_context_id(context_uuid)))

    def post(self, context_uuid : str):
        json_requests = request.get_json()
        if 'slices' in json_requests:
            json_requests = json_requests['slices']
        for slice_ in json_requests:
            if context_uuid != slice_['slice_id']['context_id']['context_uuid']['uuid']:
                raise BadRequest('Mismatching context_uuid')
        return jsonify([
            grpc_message_to_json(self.slice_client.CreateSlice(grpc_slice(slice_)))
            for slice_ in json_requests
        ])

class Slice(_Resource):
    def get(self, context_uuid : str, slice_uuid : str):
        return format_grpc_to_json(self.context_client.GetSlice(grpc_slice_id(context_uuid, slice_uuid)))

    def put(self, context_uuid : str, slice_uuid : str):
        slice_ = request.get_json()
        if context_uuid != slice_['slice_id']['context_id']['context_uuid']['uuid']:
            raise BadRequest('Mismatching context_uuid')
        if slice_uuid != slice_['slice_id']['slice_uuid']['uuid']:
            raise BadRequest('Mismatching slice_uuid')
        return format_grpc_to_json(self.slice_client.UpdateSlice(grpc_slice(slice_)))

    def delete(self, context_uuid : str, slice_uuid : str):
        return format_grpc_to_json(self.slice_client.DeleteSlice(grpc_slice_id(context_uuid, slice_uuid)))

class DeviceIds(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListDeviceIds(Empty()))

class Devices(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListDevices(Empty()))

    def post(self):
        json_requests = request.get_json()
        if 'devices' in json_requests:
            json_requests = json_requests['devices']
        return jsonify([
            grpc_message_to_json(self.device_client.AddDevice(grpc_device(device)))
            for device in json_requests
        ])

class Device(_Resource):
    def get(self, device_uuid : str):
        return format_grpc_to_json(self.context_client.GetDevice(grpc_device_id(device_uuid)))

    def put(self, device_uuid : str):
        device = request.get_json()
        if device_uuid != device['device_id']['device_uuid']['uuid']:
            raise BadRequest('Mismatching device_uuid')
        return format_grpc_to_json(self.device_client.ConfigureDevice(grpc_device(device)))

    def delete(self, device_uuid : str):
        return format_grpc_to_json(self.device_client.DeleteDevice(grpc_device_id(device_uuid)))

class LinkIds(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListLinkIds(Empty()))

class Links(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListLinks(Empty()))

    def post(self):
        json_requests = request.get_json()
        if 'links' in json_requests:
            json_requests = json_requests['links']
        json_link_ids : List[Dict] = list()
        for link_json in json_requests:
            link = grpc_link(link_json)
            if link.link_type == LinkTypeEnum.LINKTYPE_VIRTUAL:
                link_id = self.vntmanager_client.SetVirtualLink(link)
            else:
                link_id = self.context_client.SetLink(link)
            json_link_ids.append(grpc_message_to_json(link_id))
        return jsonify(json_link_ids)

class Link(_Resource):
    def get(self, link_uuid : str):
        return format_grpc_to_json(self.context_client.GetLink(grpc_link_id(link_uuid)))

    def put(self, link_uuid : str):
        link_json = request.get_json()
        link = grpc_link(link_json)
        if link_uuid != link.link_id.link_uuid.uuid:
            raise BadRequest('Mismatching link_uuid')
        if link.link_type == LinkTypeEnum.LINKTYPE_VIRTUAL:
            return format_grpc_to_json(self.vntmanager_client.SetVirtualLink(link))
        else:
            return format_grpc_to_json(self.context_client.SetLink(link))

    def delete(self, link_uuid : str):
        link_id = grpc_link_id(link_uuid)
        link = self.context_client.GetLink(link_id)
        if link.link_type == LinkTypeEnum.LINKTYPE_VIRTUAL:
            return format_grpc_to_json(self.vntmanager_client.RemoveVirtualLink(link_id))
        else:
            return format_grpc_to_json(self.context_client.RemoveLink(link_id))

class ConnectionIds(_Resource):
    def get(self, context_uuid : str, service_uuid : str):
        return format_grpc_to_json(self.context_client.ListConnectionIds(grpc_service_id(context_uuid, service_uuid)))

class Connections(_Resource):
    def get(self, context_uuid : str, service_uuid : str):
        return format_grpc_to_json(self.context_client.ListConnections(grpc_service_id(context_uuid, service_uuid)))

class Connection(_Resource):
    def get(self, connection_uuid : str):
        return format_grpc_to_json(self.context_client.GetConnection(grpc_connection_id(connection_uuid)))

class PolicyRuleIds(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListPolicyRuleIds(Empty()))

class PolicyRules(_Resource):
    def get(self):
        return format_grpc_to_json(self.context_client.ListPolicyRules(Empty()))

class PolicyRule(_Resource):
    def get(self, policy_rule_uuid : str):
        return format_grpc_to_json(self.context_client.GetPolicyRule(grpc_policy_rule_id(policy_rule_uuid)))
