import json
import logging
from datetime import date, datetime
from typing import Any, Dict, List
from uuid import uuid4
import uuid
from venv import create
from constants import Constants
from dao import events_dao, sessions_dao, charger_dao, user_dao
from errors.reservation_expired_error import ReservationExpiredError
import hooks
import utility
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import Action, RegistrationStatus
from ocpp.v16 import call_result, call
from ocpp.v16.enums import (
    ChargePointErrorCode, ChargePointStatus, Measurand, DataTransferStatus,
    ResetStatus, ResetType, ClearCacheStatus, UnlockStatus, ConfigurationStatus,
    UpdateType, UpdateStatus, DiagnosticsStatus, FirmwareStatus, MessageTrigger,
    TriggerMessageStatus, RemoteStartStopStatus, ChargingProfilePurposeType,
    AuthorizationStatus, Reason, AvailabilityStatus, AvailabilityType,
    ClearChargingProfileStatus, ChargingRateUnitType, ReservationStatus, CancelReservationStatus
)
from ocpp.v16.datatypes import MeterValue, ChargingProfile

LOGGER = logging.getLogger("server")


class ChargePoint(cp):

    def __init__(  # type: ignore
        self, id, connection, response_timeout=30
    ):
        super().__init__(id, connection, response_timeout)

    @on(Action.BootNotification)
    async def on_boot_notification(
        self,
        message_type: int,
        uuid_request: str,
        charge_point_vendor: str,
        charge_point_model: str,
        **kwargs: Any,
    ) -> call_result.BootNotificationPayload:
        current_time = datetime.utcnow().isoformat()

        charger = await charger_dao.get_charger_by_id(self.id)
        if (not charger):
            # add details to new_chargers_detail table
            new_charger = await charger_dao.get_new_charger_by_id(self.id)
            if (not new_charger):
                serial_number = kwargs["charge_box_serial_number"] if (kwargs.get("charge_box_serial_number")) else kwargs.get("charge_point_serial_number")
                firmware = kwargs.get("firmware_version")
                await charger_dao.insert_new_charger(
                    charger_id=self.id,
                    serial_number=serial_number,
                    vendor=charge_point_vendor,
                    model=charge_point_model,
                    firmware=firmware
                )

            return call_result.BootNotificationPayload(
                status=RegistrationStatus.accepted,
                current_time=current_time,
                interval=600,
            )
        await events_dao.insert_event(
            messageType_id=message_type,
            message_id=uuid_request,
            charger_id=self.id,
            event_name="BootNotification",
            json=json.dumps({
                "charge_point_vendor": charge_point_vendor,
                "charge_point_model": charge_point_model,
                "kwargs": kwargs
            }),
        )
        interval = await charger_dao.get_heartbeat_interval(self.id)
        status = RegistrationStatus.accepted
        await events_dao.insert_event(
            messageType_id=3,
            message_id=uuid_request,
            charger_id=self.id,
            event_name="BootNotification",
            json=json.dumps({
                "current_time": current_time,
                "interval": interval,
                "status": status,
            }),
        )
        return call_result.BootNotificationPayload(
            current_time=current_time,
            interval=interval,
            status=status
        )

    @on(Action.StatusNotification)
    async def on_status_notification(
        self,
        message_type: int,
        uuid_request: str,
        connector_id: int,
        error_code: ChargePointErrorCode,
        status: ChargePointStatus,
        info: str = "",
        timestamp: str = None,
        vendor_id: str = "",
        vendor_error_code: str = "",
    ) -> call_result.StatusNotificationPayload:

        timestamp = datetime.utcnow().isoformat() if (timestamp is None) else datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat()

        await events_dao.insert_event(
            messageType_id=message_type,
            message_id=uuid_request,
            charger_id=str(self.id),
            event_name="StatusNotification",
            json=json.dumps({
                "charger_id": self.id,
                "connector_id": connector_id,
                "error_code": error_code,
                "status": status,
                "info": info,
                "timestamp": timestamp,
                "vendor_id": vendor_id,
                "vendor_error_code": vendor_error_code
            }),
            connector_id=connector_id,
            created_at=timestamp
        )

        if (connector_id == 0):
            await charger_dao.update_all_connector_status(
                charger_id=str(self.id),
                status=str(status)
            )
        else:
            await charger_dao.update_connector_status(
                charger_id=str(self.id),
                connector_id=connector_id,
                status=str(status)
            )
    
        await events_dao.insert_event(
            messageType_id=3,
            message_id=uuid_request,
            charger_id=str(self.id),
            event_name="StatusNotification",
            json=json.dumps({}),
            connector_id=connector_id,
            created_at=timestamp
        )

        json_data = utility.snake_to_camel_case({
            "status_notification": {
                "charger_id": str(self.id),
                "connector_id": connector_id,
                "status": str(status)   
            } 
        })

        status = await hooks.send_values(
            data={
                "event": "Status Notification",
                "data": json_data
            }
        )

        return call_result.StatusNotificationPayload()

    @on(Action.Heartbeat)
    async def on_heartbeat(
        self,
        message_type: int,
        uuid_request: str,
    ) -> call_result.HeartbeatPayload:

        current_time = datetime.utcnow().isoformat()
        await events_dao.insert_event(
            messageType_id=message_type,
            message_id=uuid_request,
            charger_id=str(self.id),
            event_name="Heartbeat",
            json=json.dumps({
                "charger_id": self.id,
            }),
        )
        await events_dao.insert_event(
            messageType_id=3,
            message_id=uuid_request,
            charger_id=str(self.id),
            event_name="Heartbeat",
            json=json.dumps({
                "current_time": current_time,
            }),
        )
        return call_result.HeartbeatPayload(current_time)

    @on(Action.MeterValues)
    async def meter_values(
        self,
        message_type: int,
        uuid_request: str,
        connector_id: int,
        meter_value: MeterValue,
        transaction_id: int = 0
    ) -> call_result.MeterValuesPayload:

        try:
            json_data = utility.snake_to_camel_case({
                "meter_values":{
                    'connector_id': connector_id,
                    'meter_value': meter_value,
                    'transaction_id': transaction_id
                }
            })
            await events_dao.insert_event(
                messageType_id=message_type,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="MeterValues",
                connector_id=connector_id,
                json=json.dumps(
                    json_data
                ),
            )

            if (transaction_id != 0):
                energy_import_register = "0.0"
                soc = "0"
                energy_import_unit = ""
                power_import = "0.0"
                power_import_unit = ""
                context=""
                for ele in meter_value:
                    sampled_value = ele.get('sampled_value')
                    if (sampled_value):
                        for data in sampled_value:
                            if(
                                data[Constants.MEASURAND] == (
                                    Measurand.energy_active_import_register
                                )
                            ):
                                energy_import_register = data['value']
                                energy_import_unit = data['unit']
                            if (data[Constants.MEASURAND] == Measurand.soc):
                                soc = data['value']
                            if (data[Constants.MEASURAND] == Measurand.power_active_import):
                                power_import = data['value']
                                power_import_unit = data['unit']
                            if (data[Constants.CONTEXT]):
                                context = data[Constants.CONTEXT]
                await sessions_dao.save_meter_values(
                    session_id=transaction_id,
                    context=context,
                    energy_import_register=energy_import_register,
                    energy_import_unit=energy_import_unit,
                    power_import=power_import,
                    power_import_unit=power_import_unit,
                    soc=soc
                )

                status = await hooks.send_values(
                    data={
                        "event": "MeterValues",
                        "data": json_data
                    }
                )

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="MeterValues",
                connector_id=connector_id,
                json=json.dumps({}),
            )
            raise(e)

        await events_dao.insert_event(
            messageType_id=3,
            message_id=uuid_request,
            charger_id = self.id,
            event_name="MeterValues",
            connector_id=connector_id,
            json=json.dumps({}),
        )
        return call_result.MeterValuesPayload()

    @on(Action.DataTransfer)
    async def data_transfer(
        self,
        message_type: int,
        uuid_request: str,
        vendor_id: str,
        message_id: str = "",
        data: str = ""
    ) -> call_result.DataTransferPayload:

        try:
            await events_dao.insert_event(
                messageType_id=message_type,
                message_id=uuid_request,
                charger_id=self.id,
                event_name="DataTransfer",
                json=json.dumps({
                    "vendor_id": vendor_id,
                    "message_id": message_id,
                    "data": data
                }),
            )

            # TODO:implement unknownmessageId and unknownvendorId
            status = DataTransferStatus.accepted
            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id=self.id,
                event_name="DataTransfer",
                json=json.dumps({
                    "status": status
                }),
            )

            return call_result.DataTransferPayload(
                status=status
            )
        except Exception as e:
            LOGGER.error(e)
            status=DataTransferStatus.rejected
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id=self.id,
                event_name="DataTransfer",
                json=json.dumps({
                    "status": status
                }),
            )
            return call_result.DataTransferPayload(
                data="Exception occured",
                status=status
            )

    @on(Action.DiagnosticsStatusNotification)
    async def diagnostics_status_notification(
        self,
        message_type: int,
        uuid_request: str,
        status: DiagnosticsStatus
    ) -> call_result.DiagnosticsStatusNotificationPayload:
        try:
            await events_dao.insert_event(
                messageType_id=message_type,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Diagnostics Status Notification",
                json=json.dumps({
                    "status": status
                }),
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Diagnostics Status Notification",
                json=json.dumps({}),
            )
            return call_result.DiagnosticsStatusNotificationPayload()
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Diagnostics Status Notification",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    @on(Action.FirmwareStatusNotification)
    async def firmware_status_notification(
        self,
        message_type: int,
        uuid_request: str,
        status: FirmwareStatus
    ) -> call_result.FirmwareStatusNotificationPayload:
        try:
            await events_dao.insert_event(
                messageType_id=message_type,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Firmware Status Notification",
                json=json.dumps({
                    "status": status
                }),
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Firmware Status Notification",
                json=json.dumps({}),
            )
            return call_result.FirmwareStatusNotificationPayload()
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Firmware Status Notification",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    @on(Action.Authorize)
    async def on_authorize(
        self,
        message_type: int,
        uuid_request: str,
        id_tag: str
    ) -> call_result.AuthorizePayload:
        try:
            await events_dao.insert_event(
                messageType_id=message_type,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Authorize",
                json=json.dumps({
                    "id_tag": id_tag
                }),
            )

            authorization_status = await self.check_authorization(id_tag=id_tag)

            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id = self.id,
                event_name="Authorize",
                json=json.dumps({
                    "status": authorization_status
                }),
            )

            return call_result.AuthorizePayload(
                id_tag_info={
                    "status": authorization_status,

                }
            )

        except Exception as e:
            LOGGER.error(e)
            raise(e)


    @on(Action.StartTransaction)
    async def start_transaction(
        self,
        connector_id: int,
        id_tag: str,
        meter_start: float,
        message_type: int,
        uuid_request: str,
        timestamp: str,
        reservation_id: int = 0
    ) -> call_result.StartTransactionPayload:

        await events_dao.insert_event(
            messageType_id=message_type,
            message_id=uuid_request,
            charger_id=str(self.id),
            connector_id=connector_id,
            event_name="Start Transaction",
            json=json.dumps({
                "reservation_id": reservation_id,
                "id_tag": id_tag,
                "meter_start": meter_start,
            }),
            created_at=datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat(),
        )
        try:
            # check id_tag authorization
            auth_status: AuthorizationStatus = await self.check_authorization(
                id_tag=id_tag
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id=self.id,
                connector_id=connector_id,
                event_name="Start Transaction",
                json=json.dumps({
                    "status": auth_status
                }),
            )

            if (auth_status is not AuthorizationStatus.accepted):
                return call_result.StartTransactionPayload(
                    transaction_id=0,
                    id_tag_info={
                        "status": auth_status
                    }
                )

            charger_default_transaction_id = await charger_dao.get_charger_default_charging_profile_id(
                charger_id=self.id,
                connector_id=connector_id
            )

            session_id = await sessions_dao.insert_new_transaction(
                start_id_tag=id_tag,
                start_time=datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat(),
                meter_start=meter_start,
                connector_id=connector_id,
                charger_id=self.id,
                charging_profile_id=charger_default_transaction_id,
                reservation_id=reservation_id,
            )

            await hooks.send_values(
                data = {
                    "event": "ChargerStart",
                    "chargerId": self.id,
                    "connectorId": connector_id,
                    "sessionId": session_id,
                }
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=uuid_request,
                charger_id=str(self.id),
                event_name="Start Transaction",
                json=json.dumps({
                    "session_id": session_id,
                    "status": auth_status
                }),
                connector_id=connector_id,
                created_at=datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat()
            )

            return call_result.StartTransactionPayload(
                transaction_id=session_id,
                id_tag_info={
                    "status": AuthorizationStatus.accepted
                }
            )

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id=self.id,
                connector_id=connector_id,
                event_name="Start Transaction",
                json=json.dumps({
                    "session_id": 0,
                    "exception": str(e).replace("'", ""),
                    "status": AuthorizationStatus.invalid
                }),
            )
            return call_result.StartTransactionPayload(
                id_tag_info={
                    "status": AuthorizationStatus.invalid
                }
            )

    @on(Action.StopTransaction)
    async def stop_transaction(
        self,
        meter_stop: int,
        timestamp: datetime,
        transaction_id: int,
        message_type: int,
        uuid_request: str,
        id_tag: str = "",
        reason: Reason = Reason.local,
        transaction_data: MeterValue = None
    ) -> call_result.StopTransactionPayload:
        session_id = transaction_id

        await events_dao.insert_event(
            messageType_id=message_type,
            message_id=uuid_request,
            charger_id=str(self.id),
            event_name="Stop Transaction",
            json=json.dumps({
                "id_tag": id_tag,
                "transaction_data": transaction_data,
                "reason": reason,
                "meter_stop": meter_stop,
                "stop_time": datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat(),
                "session_id": session_id
            }),
        )

        try:
            # start_id_tag: str = (
            #     await sessions_dao.get_session_idtag(session_id)
            # )
            # auth_status: AuthorizationStatus = (
            #     await self.check_authorization(
            #         id_tag=id_tag, is_stop_request=True
            #     )
            # )

            await sessions_dao.update_transaction(
                session_id=session_id,
                stop_id_tag=id_tag,
                meter_stop=meter_stop,
                stop_time=datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").isoformat(),
                reason=str(reason),
                transaction_data=transaction_data,
                is_running=False
            )

            return call_result.StopTransactionPayload(
                id_tag_info={
                    "status": AuthorizationStatus.accepted
                }
            )

            await hooks.send_values(
                data = {
                    "event": "ChargerStop",
                    "chargerId": self.id,
                    "sessionId": session_id,
                }
            )

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=uuid_request,
                charger_id=self.id,
                event_name="Stop Transaction",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

# Below are the calls initated from CSMS to Charge point.

    async def check_authorization(
        self,
        id_tag: str
    ) -> AuthorizationStatus:

        try:
            global_id_tag = await charger_dao.is_global_id_tag(id_tag)
            if (global_id_tag is not None):
                return AuthorizationStatus.accepted
            rfid_details = await user_dao.get_rfid_details(id_tag)
            if (rfid_details is None):
                return AuthorizationStatus.invalid
            expiry_date: date = (
                datetime.strptime(
                    rfid_details['expiry_date'], "%Y-%m-%d").date()
                )

            if (expiry_date < date.today()):
                return AuthorizationStatus.expired

            if (rfid_details["is_blocked"]):
                return AuthorizationStatus.blocked

            # if (not is_stop_request):
            #     transaction_details = (
            #         await sessions_dao.get_running_transaction_by_rfid(
            #             id_tag
            #         )
            #     )
            #     if (transaction_details[Constants.IS_RUNNING]):
            #         return AuthorizationStatus.concurrent_tx

            customer_id = await charger_dao.get_customer_id(self.id)
            rfid_allowed_on_customer = await user_dao.is_rfid_allowed_on_network(id_tag, customer_id)
            if (not rfid_allowed_on_customer):
                return AuthorizationStatus.invalid

            if (rfid_details["user_id"]):
                return AuthorizationStatus.accepted
        except Exception as e:
            raise(e)
        return AuthorizationStatus.invalid

    async def reset_charger(
        self,
        type: ResetType,
    ) -> ResetStatus:

        unique_id:uuid = uuid4()
        reset_status: ResetStatus = None
        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id = self.id,
            event_name="Reset Charger",
            json=json.dumps({
                "type": str(type)
            }),
        )

        try:
            reset_status: ResetStatus = await self.call(
                payload=call.ResetPayload(
                    type=type
                ),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Reset Charger",
                json=json.dumps({
                    "status": reset_status.status
                }),
            )
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Reset Charger",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)
        return reset_status.status

    async def clear_cache(
        self
    ) -> ClearCacheStatus:

        unique_id:uuid = uuid4()
        clear_cache_status: ClearCacheStatus = None
        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id = self.id,
            event_name="Clear Cache",
            json=json.dumps({}),
        )

        try:
            clear_cache_status = await self.call(
                payload=call.ClearCachePayload(),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Clear Cache",
                json=json.dumps({
                    "status": clear_cache_status.status
                }),
            )
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Clear Cache",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)
        return clear_cache_status.status

    async def unlock_connector(
        self,
        connector_id: int
    ) -> call_result.UnlockConnectorPayload:
        unlock_status: UnlockStatus = None
        unique_id:uuid = uuid4()

        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id = self.id,
            event_name="Unlock Connector",
            connector_id=connector_id,
            json=json.dumps({}),
        )
        try:
            unlock_status = await self.call(
                payload=call.UnlockConnectorPayload(connector_id),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Unlock Connector",
                connector_id=connector_id,
                json=json.dumps({
                    "status": unlock_status.status
                }),
            )
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Unlock Connector",
                json=json.dumps({
                    "status": str(e).replace("'", "")
                }),
            )
            raise(e)
        return unlock_status.status

    async def get_configuration(
        self,
        config_keys: List[str]
    ):
        unique_id: uuid = uuid4()
        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id=self.id,
            event_name="Get Configuration",
            json=json.dumps({
                "config_keys": json.dumps(config_keys)
            }),
        )
        try:
            res = await self.call(
                payload=call.GetConfigurationPayload(
                    key=config_keys
                ),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Get Configuration",
                json=json.dumps({
                    "configuration_key": json.dumps(res.configuration_key).replace("'", ""),
                    "unknown_key": json.dumps(res.unknown_key)
                }),
            )
            return (res.configuration_key, res.unknown_key)
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Get Configuration",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def change_configuration(
        self,
        config_dict: Dict[str, Any]
    ):
        default_configs = config_dict
        unique_id: uuid = uuid4()
        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id=self.id,
            event_name="Change Configuration",
            json=json.dumps({
                "config_dict": json.dumps(default_configs).replace("'", "")
            }),
        )
        try:
            response_list = []
            is_reboot_required = False
            created_at = datetime.utcnow().isoformat()
            for key, value in default_configs.items():
                config_dict = {}
                response: ConfigurationStatus = await self.call(
                    payload=call.ChangeConfigurationPayload(
                        key=key,
                        value=str(value)
                    ),
                    unique_id=unique_id
                )
                config_dict["charger_id"] = self.id
                config_dict["configuration_key"] = key
                config_dict["configuration_value"] = str(value)
                config_dict["created_at"] = created_at
                config_dict["configuration_status"] = str(response)
                is_reboot_required = True if (
                    ConfigurationStatus.reboot_required == response.status
                ) else is_reboot_required
                response_list.append(config_dict)
                print(json.dumps(response_list).replace("'", ""))
                await events_dao.insert_event(
                    messageType_id=3,
                    message_id=unique_id,
                    charger_id=self.id,
                    event_name="Change Configuration",
                    json=json.dumps({
                        "response_list": json.dumps(response_list).replace("'", ""),
                        "is_reboot_required": is_reboot_required
                    }),
                )
            return response_list, is_reboot_required
                #incase we try this 
                # await charger_dao.update_configuration_status(
                #     config_list=response_list
                # )
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Change Configuration",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)
    async def get_local_list_version(self) -> int:

        unique_id:uuid = uuid4()
        try:
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Local List Version",
                json=json.dumps({}),
            )

            res = await self.call(
                payload=call.GetLocalListVersionPayload(),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Local List Version",
                json=json.dumps({
                    "local_list_version": res.list_version
                }),
            )
            return res.list_version
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Local List Version",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def send_local_list(
        self,
        local_list_version: int,
        update_type: UpdateType,
        local_authorization_list: list
    ) -> UpdateStatus:
        unique_id:uuid = uuid4()
        try:
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Send Local List Version",
                json=json.dumps({
                    "local_list_version": local_list_version,
                    "update_type": str(update_type),
                    "local_authorization_list": json.dumps(local_authorization_list)
                }),
            )
            res = await self.call(
                payload = call.SendLocalListPayload(
                    list_version=local_list_version,
                    update_type=update_type,
                    local_authorization_list=local_authorization_list
                ),
                unique_id=unique_id
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Send Local List Version",
                json=json.dumps({
                    "status": res.status
                }),
            )

            return res.status

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Send Local List Version",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def get_diagnostics(
        self,
        location: str,
        start_time: str = None,
        stop_time: str = None,
        retry_interval: int = None,
        retries: int = None,
    ) -> str:
        unique_id:uuid = uuid4()
        file_name = ""
        try:
            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=2,
                message_id=unique_id,
                event_name="GetDiagnostics",
                json=json.dumps({
                    "charger_id": self.id,
                    "location": location,
                    "retries": retries,
                    "retry_interval": retry_interval,
                    "start_time": start_time,
                    "stop_time": stop_time,
                }),
            )

            res = await self.call(
                payload=call.GetDiagnosticsPayload(
                    location=location,
                    retries=retries,
                    retry_interval=retry_interval,
                    start_time=start_time,
                    stop_time=stop_time
                ),
                unique_id=unique_id
            )

            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=3,
                message_id=unique_id,
                event_name="GetDiagnostics",
                json=json.dumps({
                    "file_name": res.file_name.replace("'", ""),
                }),
            )
            return res.file_name
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="GetDiagnostics",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def update_firmware(
        self,
        location: str,
        retrieve_date: str,
        retries: int = None,
        retry_interval: int = None,
    ) -> None:
        unique_id:uuid = uuid4()
        
        try:
            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=2,
                message_id=unique_id,
                event_name="Update Firmware",
                json=json.dumps({
                    "charger_id": self.id,
                    "location": location,
                    "retries": retries,
                    "retry_interval": retry_interval,
                    "retrieve_date": retrieve_date
                }),
            )

            await self.call(
                payload=call.UpdateFirmwarePayload(
                    location=location,
                    retries=retries,
                    retry_interval=retry_interval,
                    retrieve_date=retrieve_date
                ),
                unique_id=unique_id
            )
            
            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=3,
                message_id=unique_id,
                event_name="Update Firmware",
                json=json.dumps({}),
            )

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id = self.id,
                event_name="Update Firmware",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def trigger_message(
        self,
        requested_message_type: MessageTrigger,
        connector_id: int
    ) -> TriggerMessageStatus:
        unique_id:uuid = uuid4()
        try:
            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=2,
                message_id=unique_id,
                event_name="Trigger Message",
                json=json.dumps({
                    "charger_id": self.id,
                    "requested_message_type": requested_message_type,
                    "connector_id": connector_id
                }),
            )

            res = await self.call(
                payload=call.TriggerMessagePayload(
                    requested_message=requested_message_type,
                    connector_id=connector_id
                ),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                charger_id=self.id,
                messageType_id=3,
                message_id=unique_id,
                event_name="Trigger Message",
                json=json.dumps({
                    "status": res.status
                }),
            )
            return res.status
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                connector_id=connector_id,
                event_name="Trigger Message",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def remote_start_transaction(
        self,
        id_tag: str,
        charging_profile: ChargingProfile,
        connector_id: int,
    ) -> RemoteStartStopStatus:

        unique_id: uuid = uuid4()
        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id=self.id,
            connector_id=connector_id,
            event_name="Remote Start Transaction",
            json=json.dumps({
                "charging_profile": "" if (
                    charging_profile is None) else charging_profile,
                "id_tag": id_tag
            }),
        )
        try:
            res: RemoteStartStopStatus = await self.call(
                payload = call.RemoteStartTransactionPayload(
                    id_tag=id_tag,
                    connector_id=int(connector_id),
                    charging_profile=charging_profile if (charging_profile is not None
                                                        and
                                                        charging_profile.charging_profile_purpose is ChargingProfilePurposeType.tx_profile
                                                    ) else None
                ),
                unique_id=unique_id
            )
            print(res)

            await hooks.send_values(
                data = {
                    "event": "RemoteStart",
                    "chargerId": self.id,
                    "connectorId": connector_id,
                    "uid": id_tag,
                }
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                connector_id=connector_id,
                event_name="Remote Start Transaction",
                json=json.dumps({
                    "status": str(res.status)
                }),
            )
            return res.status
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                connector_id=connector_id,
                event_name="Remote Start Transaction",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            LOGGER.error(e)
            raise(e)

    async def remote_stop_transaction(
        self,
        session_id: int
    ) -> RemoteStartStopStatus:
        unique_id: uuid = uuid4()

        await events_dao.insert_event(
            messageType_id=2,
            message_id=unique_id,
            charger_id=self.id,
            event_name="Remote Stop Transaction",
            json=json.dumps({
                "session_id": str(session_id)
            }),
        )

        try:
            res: RemoteStartStopStatus = await self.call(
                payload=call.RemoteStopTransactionPayload(
                    transaction_id=int(session_id),
                ),
                unique_id=unique_id
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Remote Stop Transaction",
                json=json.dumps({
                    "status": str(res.status)
                }),
            )
            return res.status

            await hooks.send_values(
                data = {
                    "event": "RemoteStop",
                    "sessionId": session_id,
                }
            )

        except Exception as e:
            LOGGER.error(e)
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Remote Stop Transaction",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def change_availability(
        self,
        connector_id: int,
        type: AvailabilityType
    ) -> AvailabilityStatus:
        try:
            unique_id:uuid = uuid4()

            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Change Availability",
                json=json.dumps({
                    "connector_id": str(connector_id),
                    "type": str(type)
                }),
            )

            res = await self.call(
                payload = call.ChangeAvailabilityPayload(
                    connector_id=connector_id,
                    type=type,
                ),
                unique_id=unique_id
            )

            if (not res.status == AvailabilityStatus.rejected):
                await charger_dao.update_availability_status(
                    charger_id=self.id,
                    connector_id=connector_id,
                    availability_status=str(type.value)
                )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Change Availability",
                json=json.dumps({
                    "status": str(res.status)
                }),
            )

            return res.status
        except Exception as e:
            LOGGER.error(e)
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Remote Stop Transaction",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)


    async def set_charging_profile(
        self,
        connector_id: int,
        cs_charging_profiles: ChargingProfile
    ):
        try:

            unique_id:uuid = uuid4()
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Set Charging Profile",
                json=json.dumps({
                    "connector_id": str(connector_id)
                }),
            )

            res = await self.call(
                payload=call.SetChargingProfilePayload(
                    connector_id=connector_id,
                    cs_charging_profiles=cs_charging_profiles
                    ),
                unique_id=unique_id
                )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Set Charging Profile",
                json=json.dumps({
                    "status": str(res.status)
                }),
            )
            if (res.status == "Accepted"):
                await charger_dao.insert_new_charging_profile(
                    charging_profile_id=cs_charging_profiles.charging_profile_id,
                    charging_profile_kind=cs_charging_profiles.charging_profile_kind,
                    charging_profile_purpose=cs_charging_profiles.charging_profile_purpose,
                    charging_rate_unit=cs_charging_profiles.charging_schedule.charging_rate_unit,
                    periods=cs_charging_profiles.charging_schedule.charging_schedule_period,
                    duration=cs_charging_profiles.charging_schedule.duration,
                    min_charging_rate=cs_charging_profiles.charging_schedule.min_charging_rate,
                    recurrency_kind=cs_charging_profiles.recurrency_kind,
                    stack_level=cs_charging_profiles.stack_level,
                    start_schedule=cs_charging_profiles.charging_schedule.start_schedule,
                    valid_from=cs_charging_profiles.valid_from,
                    valid_to=cs_charging_profiles.valid_to
                )

                if (cs_charging_profiles.transaction_id is not None and cs_charging_profiles.transaction_id > 0):
                    await charger_dao.update_session_with_charging_profile_id(
                        transaction_id=cs_charging_profiles.transaction_id,
                        charging_profile_id = cs_charging_profiles.charging_profile_id
                    )
                else:
                    await charger_dao.update_charger_charging_profile(
                        charger_id=self.id,
                        connector_id=connector_id,
                        charging_profile_id=cs_charging_profiles.charging_profile_id,
                        charging_profile_purpose=cs_charging_profiles.charging_profile_purpose
                    )
            return res.status, cs_charging_profiles.charging_profile_id
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Set Charging Profiles",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def clear_charging_profile(
        self,
        id: int = None,
        connector_id: int = None,
        charging_profile_purpose: ChargingProfilePurposeType = None,
        stack_level: int = None
    ) -> ClearChargingProfileStatus:
        try:
            unique_id:uuid = uuid4()
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Clear Charging Profile",
                json=json.dumps({
                    "connector_id": str(connector_id)
                }),
            )
            res = await self.call(
                payload=call.ClearChargingProfilePayload(
                    id=id,
                    connector_id=connector_id,
                    stack_level=stack_level,
                charging_profile_purpose=charging_profile_purpose
                ),
                unique_id=unique_id
            )

            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Clear Charging Profile",
                json=json.dumps({
                    "status": str(res.status)
                }),
            )
            return res.status
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Clear Charging Profiles",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def get_composite_schedule(
        self,
        connector_id: int,
        duration: int,
        charging_rate_unit: ChargingRateUnitType
    ) -> dict:
        try:
            unique_id:uuid = uuid4()
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Composite Schedule",
                json=json.dumps({
                    "connector_id": str(connector_id),
                    "duration": str(duration),
                    "charging_rate_unit": str(charging_rate_unit)
                }),
            )
            res = await self.call(
                payload = call.GetCompositeSchedulePayload(
                    connector_id=connector_id,
                    duration=duration,
                    charging_rate_unit=charging_rate_unit
                ),
                unique_id=unique_id
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Composite Schedule",
                json=json.dumps({
                    "status": res.status
                }),
            )

            return res
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Composite Schedule",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def reserve_now(
        self,
        connector_id: int,
        expiry_date: datetime,
        id_tag: str,
        reservation_id: int,
        parent_id_tag: str
    ) -> ReservationStatus:

        try:
            unique_id:uuid = uuid4()
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Reserve Now",
                json=json.dumps({
                    "connector_id": str(connector_id),
                    "expiry_date": str(expiry_date),
                    "id_tag": str(id_tag),
                    "reservation_id": reservation_id,
                    "parent_id_tag": str(parent_id_tag)
                }),
            )
            if(reservation_id):
                exp_date = await (
                    sessions_dao.get_reservation_details(
                        reservation_id=reservation_id))
                if (exp_date is not None):
                    reservation_expiry_date = datetime.strptime(
                        exp_date, '%Y-%m-%dT%H:%M:%S.%fZ'
                    ).isoformat()

                    if(reservation_expiry_date >= datetime.utcnow()):
                        await sessions_dao.update_reservation(
                            reservation_id=reservation_id,
                            expiry_date=expiry_date
                        )
                        res = await self.call(
                            payload = call.ReserveNowPayload(
                                reservation_id=reservation_id,
                                connector_id=connector_id,
                                id_tag=id_tag,
                                parent_id_tag=parent_id_tag,
                                expiry_date=expiry_date
                            ),
                            unique_id=unique_id
                        )
                        await events_dao.insert_event(
                            messageType_id=3,
                            message_id=unique_id,
                            charger_id=self.id,
                            event_name="Reserve Now",
                            json=json.dumps({
                                "reservation_id": reservation_id,
                                "status": res.status
                            })
                        )
                        return res.status
                    else:
                        raise ReservationExpiredError(str(reservation_expiry_date))
                else:
                    raise Exception("No reservation found for this reservation id")

            reservation_id = await sessions_dao.insert_new_reservation(
                charger_id=self.id,
                connector_id=connector_id,
                id_tag=id_tag,
                parent_id_tag=parent_id_tag,
                expiry_date=expiry_date
            )
            res = await self.call(
                payload = call.ReserveNowPayload(
                    reservation_id=reservation_id,
                    connector_id=connector_id,
                    id_tag=id_tag,
                    parent_id_tag=parent_id_tag,
                    expiry_date=expiry_date
                ),
                unique_id=unique_id
            )
            await sessions_dao.update_reservation_status(
                reservation_id=reservation_id,
                status=str(res.status)
            )
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Reserve Now",
                json=json.dumps({
                    "status": res.status
                })
            )
            return res.status
        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Reserve Now",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def cancel_reservation(
        self,
        reservation_id: int
    ) -> CancelReservationStatus:
        try:
            unique_id:uuid = uuid4()
            await events_dao.insert_event(
                messageType_id=2,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Cancel Reservation",
                json=json.dumps({
                    "reservation_id": reservation_id,
                }),
            )

            res = await self.call(
                payload = call.CancelReservationPayload(
                    reservation_id=reservation_id
                ),
                unique_id=unique_id
            )

            if (res.status == CancelReservationStatus.accepted):
                await sessions_dao.cancel_reservation(reservation_id)
            
            await events_dao.insert_event(
                messageType_id=3,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Cancel Reservation",
                json=json.dumps({
                    "status": res.status
                })
            )
            return res.status

        except Exception as e:
            await events_dao.insert_event(
                messageType_id=4,
                message_id=unique_id,
                charger_id=self.id,
                event_name="Cancel Reservation",
                json=json.dumps({
                    "exception": str(e).replace("'", "")
                }),
            )
            raise(e)

    async def is_parent(self, to_check_id_tag: str, id_tag: str) -> bool:
        parent_id_tag = (
            await user_dao.get_parent_id_tag(id_tag)
        )
        if ((parent_id_tag is not None) and to_check_id_tag == parent_id_tag):
            return True
        return False
