# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# 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, List, Optional, Set, Tuple
from common.Constants import DEFAULT_CONTEXT_NAME
from common.Settings import get_setting
from common.method_wrappers.ServiceExceptions import NotFoundException
from common.proto.context_pb2 import IsolationLevelEnum, Slice, SliceId, SliceStatusEnum
from common.tools.context_queries.Context import create_context
from common.tools.context_queries.Slice import get_slice_by_uuid
from context.client.ContextClient import ContextClient
from slice.service.slice_grouper.MetricsExporter import MetricsExporter

SETTING_NAME_SLICE_GROUPING = 'SLICE_GROUPING'
TRUE_VALUES = {'Y', 'YES', 'TRUE', 'T', 'E', 'ENABLE', 'ENABLED'}

NO_ISOLATION = IsolationLevelEnum.NO_ISOLATION

def is_slice_grouping_enabled() -> bool:
    is_enabled = get_setting(SETTING_NAME_SLICE_GROUPING, default=None)
    if is_enabled is None: return False
    str_is_enabled = str(is_enabled).upper()
    return str_is_enabled in TRUE_VALUES

def create_slice_group(
    context_uuid : str, slice_name : str, capacity_gbps : float, availability : float
) -> Slice:
    slice_group_obj = Slice()
    slice_group_obj.slice_id.context_id.context_uuid.uuid = context_uuid            # pylint: disable=no-member
    slice_group_obj.slice_id.slice_uuid.uuid = slice_name                           # pylint: disable=no-member
    slice_group_obj.name = slice_name
    slice_group_obj.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_ACTIVE  # pylint: disable=no-member
    #del slice_group_obj.slice_endpoint_ids[:] # no endpoints initially
    #del slice_group_obj.slice_service_ids[:] # no sub-services
    #del slice_group_obj.slice_subslice_ids[:] # no sub-slices
    #del slice_group_obj.slice_config.config_rules[:] # no config rules
    slice_group_obj.slice_owner.owner_uuid.uuid = 'TeraFlowSDN'                     # pylint: disable=no-member
    slice_group_obj.slice_owner.owner_string = 'TeraFlowSDN'                        # pylint: disable=no-member

    constraint_sla_capacity = slice_group_obj.slice_constraints.add()               # pylint: disable=no-member
    constraint_sla_capacity.sla_capacity.capacity_gbps = capacity_gbps

    constraint_sla_availability = slice_group_obj.slice_constraints.add()           # pylint: disable=no-member
    constraint_sla_availability.sla_availability.num_disjoint_paths = 1
    constraint_sla_availability.sla_availability.all_active = True
    constraint_sla_availability.sla_availability.availability = availability

    constraint_sla_isolation = slice_group_obj.slice_constraints.add()              # pylint: disable=no-member
    constraint_sla_isolation.sla_isolation.isolation_level.append(NO_ISOLATION)

    return slice_group_obj

def create_slice_groups(
    slice_groups : List[Tuple[str, float, float]], context_uuid : str = DEFAULT_CONTEXT_NAME
) -> Dict[str, SliceId]:
    context_client = ContextClient()
    create_context(context_client, context_uuid)

    slice_group_ids : Dict[str, SliceId] = dict()
    for slice_group in slice_groups:
        slice_group_name = slice_group[0]
        slice_group_obj = get_slice_by_uuid(context_client, slice_group_name, DEFAULT_CONTEXT_NAME)
        if slice_group_obj is None:
            slice_group_obj = create_slice_group(
                DEFAULT_CONTEXT_NAME, slice_group_name, slice_group[2], slice_group[1])
            slice_group_id = context_client.SetSlice(slice_group_obj)
            slice_group_ids[slice_group_name] = slice_group_id
        else:
            slice_group_ids[slice_group_name] = slice_group_obj.slice_id

    return slice_group_ids

def get_slice_grouping_parameters(slice_obj : Slice) -> Optional[Tuple[float, float]]:
    isolation_levels : Set[int] = set()
    availability : Optional[float] = None
    capacity_gbps : Optional[float] = None

    for constraint in slice_obj.slice_constraints:
        kind = constraint.WhichOneof('constraint')
        if kind == 'sla_isolation':
            isolation_levels.update(constraint.sla_isolation.isolation_level)
        elif kind == 'sla_capacity':
            capacity_gbps = constraint.sla_capacity.capacity_gbps
        elif kind == 'sla_availability':
            availability = constraint.sla_availability.availability
        else:
            continue

    no_isolation_level = len(isolation_levels) == 0
    single_isolation_level = len(isolation_levels) == 1
    has_no_isolation_level = NO_ISOLATION in isolation_levels
    can_be_grouped = no_isolation_level or (single_isolation_level and has_no_isolation_level)
    if not can_be_grouped: return None
    if availability is None: return None
    if capacity_gbps is None: return None
    return availability, capacity_gbps

def add_slice_to_group(slice_obj : Slice, selected_group : Tuple[str, float, float]) -> bool:
    group_name, availability, capacity_gbps = selected_group
    slice_uuid = slice_obj.slice_id.slice_uuid.uuid

    context_client = ContextClient()
    slice_group_obj = get_slice_by_uuid(context_client, group_name, DEFAULT_CONTEXT_NAME, rw_copy=True)
    if slice_group_obj is None:
        raise NotFoundException('Slice', group_name, extra_details='while adding to group')

    del slice_group_obj.slice_endpoint_ids[:]
    for endpoint_id in slice_obj.slice_endpoint_ids:
        slice_group_obj.slice_endpoint_ids.add().CopyFrom(endpoint_id)

    del slice_group_obj.slice_constraints[:]
    del slice_group_obj.slice_service_ids[:]

    del slice_group_obj.slice_subslice_ids[:]
    slice_group_obj.slice_subslice_ids.add().CopyFrom(slice_obj.slice_id)

    del slice_group_obj.slice_config.config_rules[:]
    for config_rule in slice_obj.slice_config.config_rules:
        group_config_rule = slice_group_obj.slice_config.config_rules.add()
        group_config_rule.CopyFrom(config_rule)
        if config_rule.WhichOneof('config_rule') != 'custom': continue
        TEMPLATE = '/subslice[{:s}]{:s}'
        slice_resource_key = config_rule.custom.resource_key
        group_resource_key = TEMPLATE.format(slice_uuid, slice_resource_key)
        group_config_rule.custom.resource_key = group_resource_key

    context_client.SetSlice(slice_group_obj)

    metrics_exporter = MetricsExporter()
    metrics_exporter.export_point(
        slice_uuid, group_name, availability, capacity_gbps, is_center=False)

    return True

def remove_slice_from_group(slice_obj : Slice, selected_group : Tuple[str, float, float]) -> bool:
    group_name, _, _ = selected_group
    slice_uuid = slice_obj.slice_id.slice_uuid.uuid

    context_client = ContextClient()
    slice_group_obj = get_slice_by_uuid(context_client, group_name, DEFAULT_CONTEXT_NAME, rw_copy=True)
    if slice_group_obj is None:
        raise NotFoundException('Slice', group_name, extra_details='while removing from group')

    if slice_obj.slice_id in slice_group_obj.slice_subslice_ids:
        tmp_slice_group_obj = Slice()
        tmp_slice_group_obj.slice_id.CopyFrom(slice_group_obj.slice_id)             # pylint: disable=no-member

        tmp_slice_group_obj.slice_subslice_ids.add().CopyFrom(slice_obj.slice_id)   # pylint: disable=no-member

        for endpoint_id in slice_obj.slice_endpoint_ids:
            tmp_slice_group_obj.slice_endpoint_ids.add().CopyFrom(endpoint_id)      # pylint: disable=no-member

        for config_rule in slice_obj.slice_config.config_rules:
            group_config_rule = tmp_slice_group_obj.slice_config.config_rules.add() # pylint: disable=no-member
            group_config_rule.CopyFrom(config_rule)
            if group_config_rule.WhichOneof('config_rule') != 'custom': continue
            TEMPLATE = '/subslice[{:s}]{:s}'
            slice_resource_key = group_config_rule.custom.resource_key
            group_resource_key = TEMPLATE.format(slice_uuid, slice_resource_key)
            group_config_rule.custom.resource_key = group_resource_key

        context_client.UnsetSlice(tmp_slice_group_obj)

    metrics_exporter = MetricsExporter()
    metrics_exporter.delete_point(slice_uuid)
    return True
