import grpc, logging
from typing import Any, Dict, List, Optional, Tuple, Union
from common.orm.Database import Database
from common.orm.Factory import get_database_backend
from common.orm.HighLevel import get_object
from common.orm.backend.BackendEnum import BackendEnum
from context.client.ContextClient import ContextClient
from device.proto.context_pb2 import Device, DeviceId
from database.ConfigModel import set_config
from device.service.driver_api.FilterFields import FilterFieldEnum
from .database.DeviceModel import DeviceModel, DriverModel
from .DeviceTools import update_device_config_in_local_database, update_device_in_local_database

LOGGER = logging.getLogger(__name__)

class DataCache:
    def __init__(self, context_address : str, context_port : Union[str, int]) -> None:
        if context_address is None or context_port is None:
            raise Exception('Wrong address({:s}):port({:s}) of Context component'.format(
                str(context_address), str(context_port)))
        self._context_client = ContextClient(context_address, context_port)
        self._database = Database(get_database_backend(backend=BackendEnum.INMEMORY))

    def get_device(self, device_uuid : str) -> Optional[DeviceModel]:
        return get_object(self._database, DeviceModel, device_uuid, raise_if_not_found=False)

    def get_device_driver_filter_fields(self, device_uuid : str) -> Dict[FilterFieldEnum, Any]:
        db_device = self.get_device(device_uuid)
        db_driver_pks = db_device.references(DriverModel)
        db_driver_names = [DriverModel(self._database, pk).driver.value for pk,_ in db_driver_pks]
        return {
            FilterFieldEnum.DEVICE_TYPE: db_device.device_type,
            FilterFieldEnum.DRIVER     : db_driver_names,
        }

    def set_device(self, device : Device) -> Tuple[DeviceModel, bool]:
        return update_device_in_local_database(self._database, device)

    def sync_device_from_context(self, device_uuid : str) -> bool:
        try:
            device : Device = self._context_client.GetDevice(DeviceId(device_uuid={'uuid': device_uuid}))
        except grpc.RpcError as e:
            if e.value.code() != grpc.StatusCode.NOT_FOUND: raise
            return None
        return update_device_in_local_database(self._database, device)

    def sync_device_to_context(self, device_uuid : str):
        db_device = get_object(self._database, DeviceModel, device_uuid, raise_if_not_found=False)
        self._context_client.SetDevice(Device(**db_device.dump(
            include_config_rules=True, include_drivers=True, include_endpoints=True)))

    def delete_device_from_context(self, device_uuid : str):
        db_device = get_object(self._database, DeviceModel, device_uuid, raise_if_not_found=False)
        self._context_client.RemoveDevice(DeviceId(**db_device.dump_id()))

    def update_device_config_in_local_database(
        self, device_uuid : str, config_name : str, config_rules : List[Tuple[str, Any]]):
        update_device_config_in_local_database(self._database, device_uuid, config_name, config_rules)
