# 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, ForeignKey, String, Enum
from sqlalchemy.dialects.postgresql import UUID, ARRAY
from context.service.database.Base import Base
from sqlalchemy.orm import relationship
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

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_type = Column(String)
    device_config_uuid = Column(UUID(as_uuid=False), ForeignKey("Config.config_uuid"))
    device_operational_status = Column(Enum(ORM_DeviceOperationalStatusEnum, create_constraint=False,
                                            native_enum=False))

    # Relationships
    device_config = relationship("ConfigModel", lazy="joined")
    driver = relationship("DriverModel", lazy="joined")
    endpoints = relationship("EndPointModel", lazy="joined")

    # def delete(self) -> None:
    #     # pylint: disable=import-outside-toplevel
    #     from .EndPointModel import EndPointModel
    #     from .RelationModels import TopologyDeviceModel
    #
    #     for db_endpoint_pk,_ in self.references(EndPointModel):
    #         EndPointModel(self.database, db_endpoint_pk).delete()
    #
    #     for db_topology_device_pk,_ in self.references(TopologyDeviceModel):
    #         TopologyDeviceModel(self.database, db_topology_device_pk).delete()
    #
    #     for db_driver_pk,_ in self.references(DriverModel):
    #         DriverModel(self.database, db_driver_pk).delete()
    #
    #     super().delete()
    #
    #     ConfigModel(self.database, self.device_config_fk).delete()

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

    def dump_config(self) -> Dict:
        return self.device_config.dump()

    def dump_drivers(self) -> List[int]:
        return self.driver.dump()

    def dump_endpoints(self) -> List[Dict]:
        return self.endpoints.dump()

    def dump(   # pylint: disable=arguments-differ
            self, include_config_rules=True, include_drivers=False, include_endpoints=False
        ) -> Dict:
        result = {
            'device_id': self.dump_id(),
            'device_type': self.device_type,
            'device_operational_status': self.device_operational_status.value,
        }
        if include_config_rules: result.setdefault('device_config', {})['config_rules'] = self.dump_config()
        if include_drivers: result['device_drivers'] = self.dump_drivers()
        if include_endpoints: result['device_endpoints'] = self.dump_endpoints()
        return result

    def main_pk_name(self):
        return 'device_uuid'

class DriverModel(Base): # pylint: disable=abstract-method
    __tablename__ = 'Driver'
    driver_uuid = Column(UUID(as_uuid=False), primary_key=True)
    device_uuid = Column(UUID(as_uuid=False), ForeignKey("Device.device_uuid"), primary_key=True)
    driver = Column(Enum(ORM_DeviceDriverEnum, create_constraint=False, native_enum=False))

    # Relationships
    device = relationship("DeviceModel")


    def dump(self) -> Dict:
        return self.driver.value

    def main_pk_name(self):
        return 'driver_uuid'

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()
