import logging, operator
from typing import Any, List, Tuple
from common.orm.Database import Database
from common.orm.HighLevel import get_object, get_or_create_object, update_or_create_object
from common.orm.backend.Tools import key_to_str
from common.rpc_method_wrapper.ServiceExceptions import InvalidArgumentException
from context.proto.context_pb2 import ConfigActionEnum
from device.proto.context_pb2 import Device
from device.service.data_cache.database.Tools import fast_hasher, remove_dict_key
from .database.ConfigModel import ConfigModel, ConfigRuleModel, ORM_ConfigActionEnum, set_config
from .database.ContextModel import ContextModel
from .database.DeviceModel import DeviceModel, grpc_to_enum__device_operational_status, set_drivers
from .database.EndPointModel import EndPointModel
from .database.TopologyModel import TopologyModel

LOGGER = logging.getLogger(__name__)

def update_device_in_local_database(database : Database, device : Device) -> Tuple[DeviceModel, bool]:
    device_uuid = device.device_id.device_uuid.uuid

    for i,endpoint in enumerate(device.device_endpoints):
        endpoint_device_uuid = endpoint.endpoint_id.device_id.device_uuid.uuid
        if len(endpoint_device_uuid) == 0: endpoint_device_uuid = device_uuid
        if device_uuid != endpoint_device_uuid:
            raise InvalidArgumentException(
                'request.device_endpoints[{:d}].device_id.device_uuid.uuid'.format(i), endpoint_device_uuid,
                ['should be == {:s}({:s})'.format('request.device_id.device_uuid.uuid', device_uuid)])

    initial_config_result = set_config(database, device_uuid, 'initial', [])
    running_config_result = set_config(database, device_uuid, 'running', device.device_config.config_rules)
    result : Tuple[DeviceModel, bool] = update_or_create_object(database, DeviceModel, device_uuid, {
        'device_uuid'              : device_uuid,
        'device_type'              : device.device_type,
        'device_operational_status': grpc_to_enum__device_operational_status(device.device_operational_status),
        'device_initial_config_fk' : initial_config_result[0][0],
        'device_running_config_fk' : running_config_result[0][0],
    })
    db_device, updated = result
    set_drivers(database, db_device, device.device_drivers)

    for i,endpoint in enumerate(device.device_endpoints):
        endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid
        endpoint_device_uuid = endpoint.endpoint_id.device_id.device_uuid.uuid
        if len(endpoint_device_uuid) == 0: endpoint_device_uuid = device_uuid

        str_endpoint_key = key_to_str([device_uuid, endpoint_uuid])
        endpoint_attributes = {
            'device_fk'    : db_device,
            'endpoint_uuid': endpoint_uuid,
            'endpoint_type': endpoint.endpoint_type,
        }

        endpoint_topology_context_uuid = endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid
        endpoint_topology_uuid = endpoint.endpoint_id.topology_id.topology_uuid.uuid
        if len(endpoint_topology_context_uuid) > 0 and len(endpoint_topology_uuid) > 0:
            result : Tuple[ContextModel, bool] = get_or_create_object(
                database, ContextModel, endpoint_topology_context_uuid, defaults={
                    'context_uuid': endpoint_topology_context_uuid,
                })
            db_context, _ = result

            str_topology_key = key_to_str([endpoint_topology_context_uuid, endpoint_topology_uuid])
            result : Tuple[TopologyModel, bool] = get_or_create_object(
                database, TopologyModel, str_topology_key, defaults={
                    'context_fk': db_context,
                    'topology_uuid': endpoint_topology_uuid,
                })
            db_topology, _ = result

            str_endpoint_key = key_to_str([str_endpoint_key, str_topology_key], separator=':')
            endpoint_attributes['topology_fk'] = db_topology

        result : Tuple[EndPointModel, bool] = update_or_create_object(
            database, EndPointModel, str_endpoint_key, endpoint_attributes)
        db_endpoint, db_endpoint_updated = result
        updated = updated or db_endpoint_updated

    return db_device, updated

def update_device_config_in_local_database(
    database : Database, device_uuid : str, config_name : str, config_rules : List[Tuple[str, Any]]) -> None:

    str_config_key = key_to_str([device_uuid, config_name], separator=':')
    db_config = get_object(database, ConfigModel, str_config_key)
    db_config_rule_pks = db_config.references(ConfigRuleModel)
    for pk,_ in db_config_rule_pks: ConfigRuleModel(database, pk).delete()

    for position,config_rule in enumerate(config_rules):
        config_rule_resource_key, config_rule_resource_value = config_rule
        str_rule_key_hash = fast_hasher(config_rule_resource_key)
        str_config_rule_key = key_to_str([db_config.pk, str_rule_key_hash], separator=':')
        update_or_create_object(database, ConfigRuleModel, str_config_rule_key, {
            'config_fk': db_config,
            'position' : position,
            'action'   : ORM_ConfigActionEnum.SET,
            'key'      : config_rule_resource_key,
            'value'    : config_rule_resource_value,
        })
