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

from typing import Dict

# ----- Enumerations ---------------------------------------------------------------------------------------------------
def validate_config_action_enum(message):
    assert isinstance(message, str)
    assert message in [
        'CONFIGACTION_UNDEFINED',
        'CONFIGACTION_SET',
        'CONFIGACTION_DELETE',
    ]

def validate_device_driver_enum(message):
    assert isinstance(message, str)
    assert message in [
        'DEVICEDRIVER_UNDEFINED',
        'DEVICEDRIVER_OPENCONFIG',
        'DEVICEDRIVER_TRANSPORT_API',
        'DEVICEDRIVER_P4',
        'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY',
        'DEVICEDRIVER_ONF_TR_352',
    ]

def validate_device_operational_status_enum(message):
    assert isinstance(message, str)
    assert message in [
        'DEVICEOPERATIONALSTATUS_UNDEFINED',
        'DEVICEOPERATIONALSTATUS_DISABLED',
        'DEVICEOPERATIONALSTATUS_ENABLED'
    ]

def validate_kpi_sample_types_enum(message):
    assert isinstance(message, str)
    assert message in [
        'KPISAMPLETYPE_UNKNOWN',
        'KPISAMPLETYPE_PACKETS_TRANSMITTED',
        'KPISAMPLETYPE_PACKETS_RECEIVED',
        'KPISAMPLETYPE_BYTES_TRANSMITTED',
        'KPISAMPLETYPE_BYTES_RECEIVED',
    ]

def validate_service_type_enum(message):
    assert isinstance(message, str)
    assert message in [
        'SERVICETYPE_UNKNOWN',
        'SERVICETYPE_L3NM',
        'SERVICETYPE_L2NM',
        'SERVICETYPE_TAPI_CONNECTIVITY_SERVICE',
    ]

def validate_service_state_enum(message):
    assert isinstance(message, str)
    assert message in [
        'SERVICESTATUS_UNDEFINED',
        'SERVICESTATUS_PLANNED',
        'SERVICESTATUS_ACTIVE',
        'SERVICESTATUS_PENDING_REMOVAL',
    ]


# ----- Common ---------------------------------------------------------------------------------------------------------
def validate_uuid(message, allow_empty=False):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'uuid' in message
    assert isinstance(message['uuid'], str)
    if allow_empty: return
    assert len(message['uuid']) > 1

CONFIG_RULE_TYPES = {
    'custom',
    'acl',
}
def validate_config_rule(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 2
    assert 'action' in message
    validate_config_action_enum(message['action'])
    other_keys = set(list(message.keys()))
    other_keys.discard('action')
    config_rule_type = other_keys.pop()
    assert config_rule_type in CONFIG_RULE_TYPES
    assert config_rule_type == 'custom', 'ConfigRule Type Validator for {:s} not implemented'.format(config_rule_type)
    custom : Dict = message['custom']
    assert len(custom.keys()) == 2
    assert 'resource_key' in custom
    assert isinstance(custom['resource_key'], str)
    assert 'resource_value' in custom
    assert isinstance(custom['resource_value'], str)

def validate_config_rules(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'config_rules' in message
    for config_rule in message['config_rules']: validate_config_rule(config_rule)

CONSTRAINT_TYPES = {
    'custom',
    'schedule',
    'endpoint_location',
    'sla_capacity',
    'sla_latency',
    'sla_availability',
    'sla_isolation',
}
def validate_constraint(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    other_keys = list(message.keys())
    constraint_type = other_keys[0]
    assert constraint_type in CONSTRAINT_TYPES
    assert constraint_type == 'custom', 'Constraint Type Validator for {:s} not implemented'.format(constraint_type)
    custom : Dict = message['custom']
    assert len(custom.keys()) == 2
    assert 'constraint_type' in custom
    assert isinstance(custom['constraint_type'], str)
    assert 'constraint_value' in custom
    assert isinstance(custom['constraint_value'], str)


# ----- Identifiers ----------------------------------------------------------------------------------------------------

def validate_context_id(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'context_uuid' in message
    validate_uuid(message['context_uuid'])

def validate_service_id(message, context_uuid=None):
    assert isinstance(message, dict)
    assert len(message.keys()) == 2
    assert 'context_id' in message
    validate_context_id(message['context_id'])
    if context_uuid is not None: assert message['context_id']['context_uuid']['uuid'] == context_uuid
    assert 'service_uuid' in message
    validate_uuid(message['service_uuid'])

def validate_topology_id(message, context_uuid=None):
    assert isinstance(message, dict)
    assert len(message.keys()) == 2
    assert 'context_id' in message
    validate_context_id(message['context_id'])
    if context_uuid is not None: assert message['context_id']['context_uuid']['uuid'] == context_uuid
    assert 'topology_uuid' in message
    validate_uuid(message['topology_uuid'])

def validate_device_id(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'device_uuid' in message
    validate_uuid(message['device_uuid'])

def validate_link_id(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'link_uuid' in message
    validate_uuid(message['link_uuid'])

def validate_endpoint_id(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 3
    assert 'topology_id' in message
    validate_topology_id(message['topology_id'])
    assert 'device_id' in message
    validate_device_id(message['device_id'])
    assert 'endpoint_uuid' in message
    validate_uuid(message['endpoint_uuid'])

def validate_connection_id(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'connection_uuid' in message
    validate_uuid(message['connection_uuid'])


# ----- Lists of Identifiers -------------------------------------------------------------------------------------------

def validate_context_ids(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'context_ids' in message
    assert isinstance(message['context_ids'], list)
    for context_id in message['context_ids']: validate_context_id(context_id)

def validate_service_ids(message, context_uuid=None):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'service_ids' in message
    assert isinstance(message['service_ids'], list)
    for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)

def validate_topology_ids(message, context_uuid=None):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'topology_ids' in message
    assert isinstance(message['topology_ids'], list)
    for topology_id in message['topology_ids']: validate_topology_id(topology_id, context_uuid=context_uuid)

def validate_device_ids(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'device_ids' in message
    assert isinstance(message['device_ids'], list)
    for device_id in message['device_ids']: validate_device_id(device_id)

def validate_link_ids(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'link_ids' in message
    assert isinstance(message['link_ids'], list)
    for link_id in message['link_ids']: validate_link_id(link_id)

def validate_connection_ids(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'connection_ids' in message
    assert isinstance(message['connection_ids'], list)
    for connection_id in message['connection_ids']: validate_connection_id(connection_id)


# ----- Objects --------------------------------------------------------------------------------------------------------

def validate_context(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 3
    assert 'context_id' in message
    validate_context_id(message['context_id'])
    context_uuid = message['context_id']['context_uuid']['uuid']
    assert 'service_ids' in message
    assert isinstance(message['service_ids'], list)
    for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)
    assert 'topology_ids' in message
    assert isinstance(message['topology_ids'], list)
    for topology_id in message['topology_ids']: validate_topology_id(topology_id, context_uuid=context_uuid)

def validate_service_state(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'service_status' in message
    validate_service_state_enum(message['service_status'])

def validate_service(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 6
    assert 'service_id' in message
    validate_service_id(message['service_id'])
    assert 'service_type' in message
    validate_service_type_enum(message['service_type'])
    assert 'service_endpoint_ids' in message
    assert isinstance(message['service_endpoint_ids'], list)
    for endpoint_id in message['service_endpoint_ids']: validate_endpoint_id(endpoint_id)
    assert 'service_constraints' in message
    assert isinstance(message['service_constraints'], list)
    for constraint in message['service_constraints']: validate_constraint(constraint)
    assert 'service_status' in message
    validate_service_state(message['service_status'])
    assert 'service_config' in message
    validate_config_rules(message['service_config'])

def validate_topology(message, num_devices=None, num_links=None):
    assert isinstance(message, dict)
    assert len(message.keys()) == 3
    assert 'topology_id' in message
    validate_topology_id(message['topology_id'])
    assert 'device_ids' in message
    assert isinstance(message['device_ids'], list)
    if num_devices is not None: assert len(message['device_ids']) == num_devices
    for device_id in message['device_ids']: validate_device_id(device_id)
    assert 'link_ids' in message
    assert isinstance(message['link_ids'], list)
    if num_links is not None: assert len(message['link_ids']) == num_links
    for link_id in message['link_ids']: validate_link_id(link_id)

def validate_endpoint(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 3
    assert 'endpoint_id' in message
    validate_endpoint_id(message['endpoint_id'])
    assert 'endpoint_type' in message
    assert isinstance(message['endpoint_type'], str)
    assert 'kpi_sample_types' in message
    assert isinstance(message['kpi_sample_types'], list)
    for kpi_sample_type in message['kpi_sample_types']: validate_kpi_sample_types_enum(kpi_sample_type)

def validate_device(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 6
    assert 'device_id' in message
    validate_device_id(message['device_id'])
    assert 'device_type' in message
    assert isinstance(message['device_type'], str)
    assert 'device_config' in message
    validate_config_rules(message['device_config'])
    assert 'device_operational_status' in message
    validate_device_operational_status_enum(message['device_operational_status'])
    assert 'device_drivers' in message
    assert isinstance(message['device_drivers'], list)
    for driver in message['device_drivers']: validate_device_driver_enum(driver)
    assert 'device_endpoints' in message
    assert isinstance(message['device_endpoints'], list)
    for endpoint in message['device_endpoints']: validate_endpoint(endpoint)

def validate_link(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 2
    assert 'link_id' in message
    validate_link_id(message['link_id'])
    assert 'link_endpoint_ids' in message
    assert isinstance(message['link_endpoint_ids'], list)
    for endpoint_id in message['link_endpoint_ids']: validate_endpoint_id(endpoint_id)

def validate_connection(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 4
    assert 'connection_id' in message
    validate_connection_id(message['connection_id'])
    assert 'service_id' in message
    validate_service_id(message['service_id'])
    assert 'path_hops_endpoint_ids' in message
    assert isinstance(message['path_hops_endpoint_ids'], list)
    for endpoint_id in message['path_hops_endpoint_ids']: validate_endpoint_id(endpoint_id)
    assert 'sub_service_ids' in message
    assert isinstance(message['sub_service_ids'], list)
    for sub_service_id in message['sub_service_ids']: validate_service_id(sub_service_id)


# ----- Lists of Objects -----------------------------------------------------------------------------------------------

def validate_contexts(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'contexts' in message
    assert isinstance(message['contexts'], list)
    for context in message['contexts']: validate_context(context)

def validate_services(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'services' in message
    assert isinstance(message['services'], list)
    for service in message['services']: validate_service(service)

def validate_topologies(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'topologies' in message
    assert isinstance(message['topologies'], list)
    for topology in message['topologies']: validate_topology(topology)

def validate_devices(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'devices' in message
    assert isinstance(message['devices'], list)
    for device in message['devices']: validate_device(device)

def validate_links(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'links' in message
    assert isinstance(message['links'], list)
    for link in message['links']: validate_link(link)

def validate_connections(message):
    assert isinstance(message, dict)
    assert len(message.keys()) == 1
    assert 'connections' in message
    assert isinstance(message['connections'], list)
    for connection in message['connections']: validate_connection(connection)
