# 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 copy, grpc, grpc._channel, logging
from typing import Dict
from uuid import uuid4
from flask.json import jsonify
from flask_restful import Resource, request
from common.proto.context_pb2 import Empty, QoSProfileId, Uuid
from common.Constants import DEFAULT_CONTEXT_NAME
from context.client.ContextClient import ContextClient
from qos_profile.client.QoSProfileClient import QoSProfileClient
from service.client.ServiceClient import ServiceClient
from .Tools import (
    format_grpc_to_json, grpc_context_id, grpc_service_id,
    QOD_2_service, service_2_qod, grpc_message_to_qos_table_data,
    create_qos_profile_from_json
)

LOGGER = logging.getLogger(__name__)

class _Resource(Resource):
    def __init__(self) -> None:
        super().__init__()
        self.qos_profile_client = QoSProfileClient()
        self.client = ContextClient()
        self.service_client = ServiceClient()

#ProfileList Endpoint for posting
class ProfileList(_Resource):
    def post(self):
        if not request.is_json:
            return {"message": "JSON payload is required to proceed"}, 415       
        request_data: Dict = request.get_json() #get the json from the test function 
        request_data['qos_profile_id']=str(uuid4()) # Create qos ID randomly using uuid4
        # JSON TO GRPC to store the data in the grpc server
        try:
            qos_profile = create_qos_profile_from_json(request_data)
        except:
            LOGGER.exception("Error happened while creating QoS profile from json")
            return {"message": "Failed to create QoS profile"}, 500  
        # Send to gRPC server using CreateQosProfile 
        try:
            qos_profile_created = self.qos_profile_client.CreateQoSProfile(qos_profile) 
        except:
            LOGGER.exception("error happened while creating QoS profile")
        #gRPC message back to JSON using the helper function 
        qos_profile_data = grpc_message_to_qos_table_data(qos_profile_created)
        LOGGER.info(f'qos_profile_data{qos_profile_data}')       
        return jsonify(qos_profile_data)
    
    def get(self):
        qos_profiles = self.qos_profile_client.GetQoSProfiles(Empty()) #get all of type empty and defined in .proto file ()
        qos_profile_list = [] #since it iterates over QoSProfile , create a list to store all QOS profiles
        for qos_profile in qos_profiles:
            LOGGER.info('qos_profiles = {:s}'.format(str(qos_profiles)))
            qos_profile_data = grpc_message_to_qos_table_data(qos_profile) #transform to json 
            qos_profile_list.append(qos_profile_data) # append to the list 
        
        return jsonify(qos_profile_list)
    
#getting,updating,deleting using the qos profile id
class ProfileDetail(_Resource):
    def get(self, qos_profile_id):
        id = QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_id)) #this is because we want to GetQOSProfile which takes QOSProfileID
       #or
        #id=QoSProfileId()
        #id.qos_profile_id.uuid=qos_profile_id

        #The QoSProfileID is message qod_profile_id of type Uuid 
        # Uuid is a message uuid of type qos_profile_id which is a string
        try:
            qos_profile = self.qos_profile_client.GetQoSProfile(id) #get the qosprofile from grpc server according to ID 
            qos_profile_data = grpc_message_to_qos_table_data(qos_profile) # grpc to json agian to view it on http
            return jsonify(qos_profile_data)
        except grpc._channel._InactiveRpcError as exc:
            if exc.code() == grpc.StatusCode.NOT_FOUND:
                LOGGER.warning(f"QoSProfile not found: {qos_profile_id}")
                return {"error": f"QoSProfile {qos_profile_id} not found"}, 404
            LOGGER.exception("gRPC error while fetching QoSProfile")
            return {"error": "Internal Server Error"}, 500
        except:
            LOGGER.exception("Error while fetching QoSProfile")
            return {"error": "Internal Server Error"}, 500

    def put(self, qos_profile_id):
        try:
            request_data = request.get_json() # get the json to do the update
            if 'qos_profile_id' not in request_data: #ensuring the update on qos profile id
                request_data['qos_profile_id'] = qos_profile_id  # ID to be updated            
            qos_profile = create_qos_profile_from_json(request_data) # Transform it again to grpc 
            qos_profile_updated = self.qos_profile_client.UpdateQoSProfile(qos_profile) # update the profile in the grpc server
            return grpc_message_to_qos_table_data(qos_profile_updated), 200
        except KeyError as e:
            LOGGER.exception("Missing required key")
            return {"error": f"Missing required key: {str(e)}"}, 400
        except grpc._channel._InactiveRpcError as exc:
            if exc.code() == grpc.StatusCode.NOT_FOUND:
                LOGGER.warning(f"QoSProfile not found for update: {qos_profile_id}")
                return {"error": f"QoSProfile {qos_profile_id} not found"}, 404
            LOGGER.exception("gRPC error while updating QoSProfile")
            return {"error": "Internal Server Error"}, 500
        except:
            LOGGER.exception(f"Error in PUT /profiles/{qos_profile_id}")
            return {"error": "Internal Server Error"}, 500

    def delete(self, qos_profile_id):
        id = QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_id)) # get id to delete accordingly
        try:
            qos_profile = self.qos_profile_client.DeleteQoSProfile(id)
            qos_profile_data = grpc_message_to_qos_table_data(qos_profile)
            return jsonify(qos_profile_data)
        except grpc._channel._InactiveRpcError as exc:
            if exc.code() == grpc.StatusCode.NOT_FOUND:
                LOGGER.warning(f"QoSProfile not found for deletion: {qos_profile_id}")
                return {"error": f"QoSProfile {qos_profile_id} not found"}, 404
            LOGGER.error(f"gRPC error while deleting QoSProfile: {exc}")
            return {"error": "Internal Server Error"}, 500
        except:
            LOGGER.exception(f"Error in DELETE /profiles/{qos_profile_id}")
            return {"error": "Internal Server Error"}, 500

### SESSION ##########################################################

class QodInfo(_Resource):
    def post(self):
        if not request.is_json:
            return jsonify({'error': 'Unsupported Media Type', 'message': 'JSON payload is required'}), 415
        request_data: Dict = request.get_json()
        qos_profile_id = request_data.get('qos_profile_id')
        qos_session_id = request_data.get('qos_session_id')
        duration = request_data.get('duration')
        LOGGER.info(f'qos_profile_id:{qos_profile_id}')
        if not qos_profile_id:
            return jsonify({'error': 'qos_profile_id is required'}), 400
        if qos_session_id:
            return jsonify({'error': 'qos_session_id is not allowed in creation'}), 400
        service = QOD_2_service(self.client, request_data, qos_profile_id, duration)
        stripped_service = copy.deepcopy(service)
        stripped_service.ClearField('service_endpoint_ids')
        stripped_service.ClearField('service_constraints')
        stripped_service.ClearField('service_config')
        try:
            create_response = format_grpc_to_json(self.service_client.CreateService(stripped_service))
            update_response = format_grpc_to_json(self.service_client.UpdateService(service))
            response = {
                "create_response": create_response,
                "update_response": update_response
            }
        except Exception as e:
            LOGGER.error(f"Unexpected error: {str(e)}", exc_info=True)
            return jsonify({'error': 'Internal Server Error', 'message': 'An unexpected error occurred'}), 500
        return response
        #didnt work return jsonify(response)

    def get(self):
        service_list = self.client.ListServices(grpc_context_id(DEFAULT_CONTEXT_NAME)) #return context id as json
        qod_info = [service_2_qod(service) for service in service_list.services] #iterating over service list 
        LOGGER.info(f"error related to qod_info: {qod_info}")
        return qod_info
        #didnt work return jsonify(qod_info)

class QodInfoID(_Resource):
    def get(self, sessionId: str):
        try:
            service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, sessionId))
            return service_2_qod(service)
        except grpc._channel._InactiveRpcError as exc:
            if exc.code()==grpc.StatusCode.NOT_FOUND:
                LOGGER.warning(f"Qod Session not found: {sessionId}")
                return {"error": f"Qod Session {sessionId} not found"}, 404

    def put(self, sessionId: str):
        try:
            request_data: Dict = request.get_json()
            session_id = request_data.get('session_id')
            if not session_id:
                return jsonify({'error': 'sessionId is required'}), 400
            qos_profile_id = request_data.get('qos_profile_id')
            if not qos_profile_id:
                return jsonify({'error': 'qos_profile_id is required'}), 400
            duration = request_data.get('duration')
            service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, sessionId)) #to get service we should have the context and the session id
            if qos_profile_id:
                service.name = qos_profile_id  # if we provide a new qos profile , update the service name with new qos_profile_id
            if duration:  
                for constraint in service.service_constraints:
                    if constraint.WhichOneof('constraint') == 'schedule':
                        constraint.schedule.duration_days = duration            
            updated_service = self.service_client.UpdateService(service)
            qod_response = service_2_qod(updated_service)
            return qod_response, 200
        except KeyError as e:
            LOGGER.error(f"Missing required key: {e}")
            return {"error": f"Missing required key: {str(e)}"}, 400
        except grpc._channel._InactiveRpcError as exc:
            if exc.code() == grpc.StatusCode.NOT_FOUND:
                LOGGER.warning(f"Qod Session not found: {sessionId}")
                return {"error": f"Qod Session {sessionId} not found"}, 404
            LOGGER.error(f"gRPC error while updating Qod Session: {exc}")
            return {"error": "Internal Server Error"}, 500
        except:
            LOGGER.exception(f"Error in PUT /sessions/{sessionId}")
            return {"error": "Internal Server Error"}, 500

    def delete(self, sessionId: str):
        self.service_client.DeleteService(grpc_service_id(DEFAULT_CONTEXT_NAME, sessionId))
        return{"Session Deleted"}
