# 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.

import enum
import functools, logging
#import uuid
from typing import Dict #, List
#from common.orm.Database import Database
#from common.orm.backend.Tools import key_to_str
from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum
from sqlalchemy import Column, Float, ForeignKey, String, Enum
from sqlalchemy.dialects.postgresql import UUID, ARRAY
from sqlalchemy.orm import relationship
from context.service.database._Base import _Base
from .Tools import grpc_to_enum

LOGGER = logging.getLogger(__name__)

class ORM_DeviceDriverEnum(enum.Enum):
    UNDEFINED             = DeviceDriverEnum.DEVICEDRIVER_UNDEFINED
    OPENCONFIG            = DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG
    TRANSPORT_API         = DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API
    P4                    = DeviceDriverEnum.DEVICEDRIVER_P4
    IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY
    ONF_TR_352            = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352
    XR                    = DeviceDriverEnum.DEVICEDRIVER_XR

grpc_to_enum__device_driver = functools.partial(
    grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)

class ORM_DeviceOperationalStatusEnum(enum.Enum):
    UNDEFINED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED
    DISABLED  = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED
    ENABLED   = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED

grpc_to_enum__device_operational_status = functools.partial(
    grpc_to_enum, DeviceOperationalStatusEnum, ORM_DeviceOperationalStatusEnum)

class DeviceModel(_Base):
    __tablename__ = 'device'
    device_uuid = Column(UUID(as_uuid=False), primary_key=True)
    device_name = Column(String(), nullable=False)
    device_type = Column(String(), nullable=False)
    #device_config_uuid = Column(UUID(as_uuid=False), ForeignKey('config.config_uuid', ondelete='CASCADE'))
    device_operational_status = Column(Enum(ORM_DeviceOperationalStatusEnum))
    device_drivers = Column(ARRAY(Enum(ORM_DeviceDriverEnum), dimensions=1))
    created_at = Column(Float)

    # Relationships
    topology_device = relationship('TopologyDeviceModel', back_populates='devices')
    #device_config = relationship("ConfigModel", passive_deletes=True, lazy="joined")
    endpoints = relationship('EndPointModel', passive_deletes=True, back_populates='device')

    def dump_id(self) -> Dict:
        return {'device_uuid': {'uuid': self.device_uuid}}

    def dump(self) -> Dict:
        return {
            'device_id'                : self.dump_id(),
            'name'                     : self.device_name,
            'device_type'              : self.device_type,
            'device_operational_status': self.device_operational_status.value,
            'device_drivers'           : [d.value for d in self.device_drivers],
            #'device_config'            : {'config_rules': self.device_config.dump()},
            #'device_endpoints'         : [ep.dump() for ep in self.endpoints],
        }

#def set_drivers(database : Database, db_device : DeviceModel, grpc_device_drivers):
#    db_device_pk = db_device.device_uuid
#    for driver in grpc_device_drivers:
#        orm_driver = grpc_to_enum__device_driver(driver)
#        str_device_driver_key = key_to_str([db_device_pk, orm_driver.name])
#        db_device_driver = DriverModel(database, str_device_driver_key)
#        db_device_driver.device_fk = db_device
#        db_device_driver.driver = orm_driver
#        db_device_driver.save()

#    def set_kpi_sample_types(self, db_endpoint: EndPointModel, grpc_endpoint_kpi_sample_types):
#        db_endpoint_pk = db_endpoint.endpoint_uuid
#        for kpi_sample_type in grpc_endpoint_kpi_sample_types:
#            orm_kpi_sample_type = grpc_to_enum__kpi_sample_type(kpi_sample_type)
#            # str_endpoint_kpi_sample_type_key = key_to_str([db_endpoint_pk, orm_kpi_sample_type.name])
#            data = {'endpoint_uuid': db_endpoint_pk,
#                    'kpi_sample_type': orm_kpi_sample_type.name,
#                    'kpi_uuid': str(uuid.uuid4())}
#            db_endpoint_kpi_sample_type = KpiSampleTypeModel(**data)
#            self.database.create(db_endpoint_kpi_sample_type)

#    def set_drivers(self, db_device: DeviceModel, grpc_device_drivers):
#        db_device_pk = db_device.device_uuid
#        for driver in grpc_device_drivers:
#            orm_driver = grpc_to_enum__device_driver(driver)
#            str_device_driver_key = key_to_str([db_device_pk, orm_driver.name])
#            driver_config = {
#                # "driver_uuid": str(uuid.uuid4()),
#                "device_uuid": db_device_pk,
#                "driver": orm_driver.name
#            }
#            db_device_driver = DriverModel(**driver_config)
#            db_device_driver.device_fk = db_device
#            db_device_driver.driver = orm_driver
#
#            self.database.create_or_update(db_device_driver)

#    def update_config(
#            self, session, db_parent_pk: str, config_name: str,
#            raw_config_rules: List[Tuple[ORM_ConfigActionEnum, str, str]]
#    ) -> List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]]:
#
#        created = False
#
#        db_config = session.query(ConfigModel).filter_by(**{ConfigModel.main_pk_name(): db_parent_pk}).one_or_none()
#        if not db_config:
#            db_config = ConfigModel()
#            setattr(db_config, ConfigModel.main_pk_name(), db_parent_pk)
#            session.add(db_config)
#            session.commit()
#            created = True
#
#        LOGGER.info('UPDATED-CONFIG: {}'.format(db_config.dump()))
#
#        db_objects: List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]] = [(db_config, created)]
#
#        for position, (action, resource_key, resource_value) in enumerate(raw_config_rules):
#            if action == ORM_ConfigActionEnum.SET:
#                result : Tuple[ConfigRuleModel, bool] = self.set_config_rule(
#                    db_config, position, resource_key, resource_value)
#                db_config_rule, updated = result
#                db_objects.append((db_config_rule, updated))
#            elif action == ORM_ConfigActionEnum.DELETE:
#                self.delete_config_rule(db_config, resource_key)
#            else:
#                msg = 'Unsupported action({:s}) for resource_key({:s})/resource_value({:s})'
#                raise AttributeError(
#                    msg.format(str(ConfigActionEnum.Name(action)), str(resource_key), str(resource_value)))
#
#        return db_objects
#
#    def set_config_rule(self, db_config: ConfigModel, position: int, resource_key: str, resource_value: str,
#    ):  # -> Tuple[ConfigRuleModel, bool]:
#
#        from src.context.service.database.Tools import fast_hasher
#        str_rule_key_hash = fast_hasher(resource_key)
#        str_config_rule_key = key_to_str([db_config.config_uuid, str_rule_key_hash], separator=':')
#        pk = str(uuid.uuid5(uuid.UUID('9566448d-e950-425e-b2ae-7ead656c7e47'), str_config_rule_key))
#        data = {'config_rule_uuid': pk, 'config_uuid': db_config.config_uuid, 'position': position,
#                'action': ORM_ConfigActionEnum.SET, 'key': resource_key, 'value': resource_value}
#        to_add = ConfigRuleModel(**data)
#
#        result, updated = self.database.create_or_update(to_add)
#        return result, updated
#
#    def delete_config_rule(
#            self, db_config: ConfigModel, resource_key: str
#    ) -> None:
#
#        from src.context.service.database.Tools import fast_hasher
#        str_rule_key_hash = fast_hasher(resource_key)
#        str_config_rule_key = key_to_str([db_config.pk, str_rule_key_hash], separator=':')
#
#        db_config_rule = self.database.get_object(ConfigRuleModel, str_config_rule_key, raise_if_not_found=False)
#
#        if db_config_rule is None:
#            return
#        db_config_rule.delete()
#
#    def delete_all_config_rules(self, db_config: ConfigModel) -> None:
#
#        db_config_rule_pks = db_config.references(ConfigRuleModel)
#        for pk, _ in db_config_rule_pks: ConfigRuleModel(self.database, pk).delete()
#
#        """
#        for position, (action, resource_key, resource_value) in enumerate(raw_config_rules):
#            if action == ORM_ConfigActionEnum.SET:
#                result: Tuple[ConfigRuleModel, bool] = set_config_rule(
#                    database, db_config, position, resource_key, resource_value)
#                db_config_rule, updated = result
#                db_objects.append((db_config_rule, updated))
#            elif action == ORM_ConfigActionEnum.DELETE:
#                delete_config_rule(database, db_config, resource_key)
#            else:
#                msg = 'Unsupported action({:s}) for resource_key({:s})/resource_value({:s})'
#                raise AttributeError(
#                    msg.format(str(ConfigActionEnum.Name(action)), str(resource_key), str(resource_value)))
#
#        return db_objects
#        """