/*
* Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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.
*/

package eu.teraflow.policy;

import eu.teraflow.policy.context.ContextService;
import eu.teraflow.policy.context.model.ConfigActionEnum;
import eu.teraflow.policy.context.model.ConfigRule;
import eu.teraflow.policy.context.model.ConfigRuleCustom;
import eu.teraflow.policy.context.model.ConfigRuleTypeCustom;
import eu.teraflow.policy.context.model.Constraint;
import eu.teraflow.policy.context.model.ConstraintCustom;
import eu.teraflow.policy.context.model.ConstraintTypeCustom;
import eu.teraflow.policy.context.model.ServiceConfig;
import eu.teraflow.policy.device.DeviceService;
import eu.teraflow.policy.model.BooleanOperator;
import eu.teraflow.policy.model.PolicyRule;
import eu.teraflow.policy.model.PolicyRuleAction;
import eu.teraflow.policy.model.PolicyRuleActionConfig;
import eu.teraflow.policy.model.PolicyRuleActionEnum;
import eu.teraflow.policy.model.PolicyRuleBasic;
import eu.teraflow.policy.model.PolicyRuleCondition;
import eu.teraflow.policy.model.PolicyRuleDevice;
import eu.teraflow.policy.model.PolicyRuleService;
import eu.teraflow.policy.model.PolicyRuleState;
import eu.teraflow.policy.model.PolicyRuleStateEnum;
import eu.teraflow.policy.model.PolicyRuleTypeDevice;
import eu.teraflow.policy.model.PolicyRuleTypeService;
import eu.teraflow.policy.monitoring.MonitoringService;
import eu.teraflow.policy.monitoring.model.AlarmDescriptor;
import eu.teraflow.policy.monitoring.model.AlarmResponse;
import eu.teraflow.policy.monitoring.model.AlarmSubscription;
import eu.teraflow.policy.monitoring.model.KpiValueRange;
import eu.teraflow.policy.service.ServiceService;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.groups.UniJoin;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.jboss.logging.Logger;

@ApplicationScoped
public class PolicyServiceImpl implements PolicyService {

    private static final Logger LOGGER = Logger.getLogger(PolicyServiceImpl.class);
    private static final String INVALID_MESSAGE = "%s is invalid.";
    private static final String VALID_MESSAGE = "%s is valid.";
    private static final int POLICY_EVALUATION_TIMEOUT = 5;
    private static final int ACCEPTABLE_NUMBER_OF_ALARMS = 3;
    private static final int MONITORING_WINDOW_IN_SECONDS = 5;
    private static final int SAMPLING_RATE_PER_SECOND = 1;
    // TODO: Find a better way to disregard alarms while reconfiguring path
    // Temporary solution for not calling the same rpc more than it's needed
    private static int noAlarms = 0;

    private static final PolicyRuleState INSERTED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_INSERTED, "Successfully entered to INSERTED state");
    private static final PolicyRuleState VALIDATED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_VALIDATED, "Successfully transitioned to VALIDATED state");
    private static final PolicyRuleState PROVISIONED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_PROVISIONED,
                    "Successfully transitioned from VALIDATED to PROVISIONED state");
    private static final PolicyRuleState ACTIVE_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_ACTIVE,
                    "Successfully transitioned from PROVISIONED to ACTIVE state");
    private static final PolicyRuleState ENFORCED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_ENFORCED,
                    "Successfully transitioned from ACTIVE to ENFORCED state");
    private static final PolicyRuleState INEFFECTIVE_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_INEFFECTIVE,
                    "Transitioned from ENFORCED to INEFFECTIVE state");
    private static final PolicyRuleState EFFECTIVE_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_EFFECTIVE,
                    "Successfully transitioned from ENFORCED to EFFECTIVE state");
    private static final PolicyRuleState UPDATED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_UPDATED, "Successfully entered to UPDATED state");
    private static final PolicyRuleState REMOVED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_REMOVED, "Successfully entered to REMOVED state");

    private final ContextService contextService;
    private final MonitoringService monitoringService;
    private final ServiceService serviceService;
    private final DeviceService deviceService;
    private final PolicyRuleConditionValidator policyRuleConditionValidator;
    private final PolicyRuleConditionFieldsGetter policyRuleConditionFieldsGetter;

    private HashMap<String, PolicyRuleAction> policyRuleActionMap = new HashMap<>();
    private ConcurrentHashMap<String, PolicyRuleService> alarmPolicyRuleServiceMap =
            new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, PolicyRuleDevice> alarmPolicyRuleDeviceMap =
            new ConcurrentHashMap<>();

    @Inject
    public PolicyServiceImpl(
            ContextService contextService,
            MonitoringService monitoringService,
            ServiceService serviceService,
            DeviceService deviceService,
            PolicyRuleConditionValidator policyRuleConditionValidator,
            PolicyRuleConditionFieldsGetter policyRuleConditionFieldsGetter) {
        this.contextService = contextService;
        this.monitoringService = monitoringService;
        this.serviceService = serviceService;
        this.deviceService = deviceService;
        this.policyRuleConditionValidator = policyRuleConditionValidator;
        this.policyRuleConditionFieldsGetter = policyRuleConditionFieldsGetter;
    }

    private static String gen() {
        Random r = new Random(System.currentTimeMillis());
        return String.valueOf((1 + r.nextInt(2)) * 10000 + r.nextInt(10000));
    }

    private static double getTimeStamp() {
        long now = Instant.now().getEpochSecond();
        return Long.valueOf(now).doubleValue();
    }

    @Override
    public Uni<PolicyRuleState> addPolicyService(PolicyRuleService policyRuleService) {
        LOGGER.infof("Received %s", policyRuleService);

        if (!policyRuleService.areArgumentsValid()) {
            LOGGER.error(policyRuleService.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleService.getExeceptionMessage());

            return Uni.createFrom().item(policyRuleState);
        }

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        if (!policyRuleBasic.areArgumentsValid()) {
            LOGGER.error(policyRuleService.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleBasic.getExeceptionMessage());
            return Uni.createFrom().item(policyRuleState);
        }

        final var serviceId = policyRuleService.getServiceId();
        final var deviceIds = policyRuleService.getDeviceIds();
        final var isServiceValid = policyRuleConditionValidator.isServiceIdValid(serviceId, deviceIds);

        return isServiceValid
                .onItem()
                .transform(
                        isService -> {
                            if (!isService) {
                                var policyRuleState =
                                        new PolicyRuleState(
                                                PolicyRuleStateEnum.POLICY_FAILED,
                                                String.format(INVALID_MESSAGE, serviceId));

                                return policyRuleState;
                            }

                            final var policyRuleTypeService = new PolicyRuleTypeService(policyRuleService);
                            final var policyRule = new PolicyRule(policyRuleTypeService);
                            final var alarmDescriptorList = createAlarmDescriptorList(policyRule);

                            if (alarmDescriptorList.isEmpty()) {
                                var policyRuleState =
                                        new PolicyRuleState(
                                                PolicyRuleStateEnum.POLICY_FAILED,
                                                String.format(
                                                        "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
                                                        policyRuleBasic.getPolicyRuleId()));
                                return policyRuleState;
                            }
                            contextService.setPolicyRule(policyRule).subscribe().with(x -> {});
                            setPolicyRuleServiceToContext(policyRuleService, VALIDATED_POLICYRULE_STATE);
                            noAlarms = 0;

                            // Create an alarmIds list that contains the promised ids returned from setKpiAlarm
                            List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
                            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
                                LOGGER.infof("alarmDescriptor:");
                                LOGGER.infof(alarmDescriptor.toString());
                                alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
                            }

                            // Transform the alarmIds into promised alarms returned from the
                            // getAlarmResponseStream
                            List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
                            for (Uni<String> alarmId : alarmIds) {
                                alarmResponseStreamList.add(
                                        alarmId
                                                .onItem()
                                                .transformToMulti(
                                                        id -> {
                                                            alarmPolicyRuleServiceMap.put(id, policyRuleService);

                                                            // TODO: Create infinite subscription
                                                            var alarmSubscription = new AlarmSubscription(id, 259200, 5000);
                                                            return monitoringService.getAlarmResponseStream(alarmSubscription);
                                                        }));
                            }

                            // Merge the promised alarms into one stream (Multi Object)
                            final var multi = Multi.createBy().merging().streams(alarmResponseStreamList);
                            setPolicyRuleServiceToContext(policyRuleService, PROVISIONED_POLICYRULE_STATE);

                            monitorAlarmResponseForService(multi);

                            // TODO: Resubscribe to the stream, if it has ended

                            // TODO: Redesign evaluation of action
                            // evaluateAction(policyRule, alarmDescriptorList, multi);

                            return VALIDATED_POLICYRULE_STATE;
                        });
    }

    @Override
    public Uni<PolicyRuleState> addPolicyDevice(PolicyRuleDevice policyRuleDevice) {
        LOGGER.infof("Received %s", policyRuleDevice);

        if (!policyRuleDevice.areArgumentsValid()) {
            LOGGER.error(policyRuleDevice.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleDevice.getExeceptionMessage());

            return Uni.createFrom().item(policyRuleState);
        }

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        if (!policyRuleBasic.areArgumentsValid()) {
            LOGGER.error(policyRuleDevice.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleBasic.getExeceptionMessage());
            return Uni.createFrom().item(policyRuleState);
        }

        final var deviceIds = policyRuleDevice.getDeviceIds();
        final var areDevicesValid = returnInvalidDeviceIds(deviceIds);

        return areDevicesValid
                .onItem()
                .transform(
                        areDevices -> {
                            if (areDevices.contains(false)) {
                                var policyRuleState =
                                        new PolicyRuleState(
                                                PolicyRuleStateEnum.POLICY_FAILED,
                                                String.format(
                                                        INVALID_MESSAGE,
                                                        policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId()));

                                return policyRuleState;
                            }

                            final var policyRuleTypeDevice = new PolicyRuleTypeDevice(policyRuleDevice);
                            final var policyRule = new PolicyRule(policyRuleTypeDevice);

                            final var alarmDescriptorList = createAlarmDescriptorList(policyRule);
                            if (alarmDescriptorList.isEmpty()) {
                                var policyRuleState =
                                        new PolicyRuleState(
                                                PolicyRuleStateEnum.POLICY_FAILED,
                                                String.format(
                                                        "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
                                                        policyRuleBasic.getPolicyRuleId()));
                                return policyRuleState;
                            }

                            contextService.setPolicyRule(policyRule).subscribe().with(x -> {});
                            setPolicyRuleDeviceToContext(policyRuleDevice, VALIDATED_POLICYRULE_STATE);
                            noAlarms = 0;

                            List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
                            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
                                LOGGER.infof("alarmDescriptor:");
                                LOGGER.infof(alarmDescriptor.toString());
                                alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
                            }

                            // Transform the alarmIds into promised alarms returned from the
                            // getAlarmResponseStream
                            List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
                            for (Uni<String> alarmId : alarmIds) {
                                alarmResponseStreamList.add(
                                        alarmId
                                                .onItem()
                                                .transformToMulti(
                                                        id -> {
                                                            alarmPolicyRuleDeviceMap.put(id, policyRuleDevice);

                                                            // TODO: Create infinite subscription
                                                            var alarmSubscription = new AlarmSubscription(id, 259200, 5000);
                                                            return monitoringService.getAlarmResponseStream(alarmSubscription);
                                                        }));
                            }

                            // Merge the promised alarms into one stream (Multi Object)
                            final var multi = Multi.createBy().merging().streams(alarmResponseStreamList);
                            setPolicyRuleDeviceToContext(policyRuleDevice, PROVISIONED_POLICYRULE_STATE);

                            monitorAlarmResponseForDevice(multi);

                            // TODO: Resubscribe to the stream, if it has ended

                            // TODO: Redesign evaluation of action
                            // evaluateAction(policyRule, alarmDescriptorList, multi);

                            return VALIDATED_POLICYRULE_STATE;
                        });
    }

    @Override
    public Uni<PolicyRuleState> updatePolicyService(PolicyRuleService policyRuleService) {
        LOGGER.infof("Received %s", policyRuleService);

        if (!policyRuleService.areArgumentsValid()) {
            LOGGER.error(policyRuleService.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleService.getExeceptionMessage());

            return Uni.createFrom().item(policyRuleState);
        }

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        if (!policyRuleBasic.areArgumentsValid()) {
            LOGGER.error(policyRuleService.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleBasic.getExeceptionMessage());
            return Uni.createFrom().item(policyRuleState);
        }

        final var serviceId = policyRuleService.getServiceId();
        final var policyRuleId = policyRuleBasic.getPolicyRuleId();
        final var isPolicyRuleServiceValid =
                policyRuleConditionValidator.isPolicyRuleServiceValid(policyRuleId, serviceId);

        return isPolicyRuleServiceValid
                .onItem()
                .transform(
                        isPolicyRuleService -> {
                            if (!isPolicyRuleService) {
                                return new PolicyRuleState(
                                        PolicyRuleStateEnum.POLICY_FAILED, String.format(INVALID_MESSAGE, serviceId));
                            }

                            return VALIDATED_POLICYRULE_STATE;
                        });
    }

    @Override
    public Uni<PolicyRuleState> updatePolicyDevice(PolicyRuleDevice policyRuleDevice) {
        LOGGER.infof("Received %s", policyRuleDevice);

        if (!policyRuleDevice.areArgumentsValid()) {
            LOGGER.error(policyRuleDevice.getExeceptionMessage());
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleDevice.getExeceptionMessage());

            return Uni.createFrom().item(policyRuleState);
        }

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        if (!policyRuleBasic.areArgumentsValid()) {
            final var policyRuleState =
                    new PolicyRuleState(
                            PolicyRuleStateEnum.POLICY_FAILED, policyRuleBasic.getExeceptionMessage());
            return Uni.createFrom().item(policyRuleState);
        }

        final var policyRuleId = policyRuleBasic.getPolicyRuleId();
        final var isPolicyRuleValid =
                policyRuleConditionValidator.isUpdatedPolicyRuleIdValid(policyRuleId);

        return isPolicyRuleValid
                .onItem()
                .transform(
                        isPolicyRuleService -> {
                            if (!isPolicyRuleService) {
                                return new PolicyRuleState(
                                        PolicyRuleStateEnum.POLICY_FAILED,
                                        String.format(INVALID_MESSAGE, policyRuleId));
                            }

                            return VALIDATED_POLICYRULE_STATE;
                        });
    }

    @Override
    public Uni<PolicyRuleState> deletePolicy(String policyRuleId) {
        LOGGER.infof("Received %s", policyRuleId);

        final var getPolicyRule = contextService.getPolicyRule(policyRuleId);

        return getPolicyRule
                .onItem()
                .transform(
                        policyRule -> {
                            var policyRuleBasic = policyRule.getPolicyRuleType().getPolicyRuleBasic();

                            policyRule
                                    .getPolicyRuleType()
                                    .getPolicyRuleBasic()
                                    .setPolicyRuleState(REMOVED_POLICYRULE_STATE);

                            contextService
                                    .setPolicyRule(policyRule)
                                    .subscribe()
                                    .with(
                                            tmp ->
                                                    LOGGER.infof(
                                                            "DeletePolicy with id: " + VALID_MESSAGE,
                                                            policyRuleBasic.getPolicyRuleId()));

                            return policyRuleBasic.getPolicyRuleState();
                        });
    }

    private Uni<List<Boolean>> returnInvalidDeviceIds(List<String> deviceIds) {
        UniJoin.Builder<Boolean> builder = Uni.join().builder();
        for (String deviceId : deviceIds) {
            final var validatedDeviceId = policyRuleConditionValidator.isDeviceIdValid(deviceId);
            builder.add(validatedDeviceId);
        }
        return builder.joinAll().andFailFast();
    }

    private List<AlarmDescriptor> createAlarmDescriptorList(PolicyRule policyRule) {
        final var policyRuleType = policyRule.getPolicyRuleType();
        final var policyRuleTypeSpecificType = policyRuleType.getPolicyRuleType();

        List<AlarmDescriptor> alarmDescriptorList = new ArrayList<>();
        if (policyRuleTypeSpecificType instanceof PolicyRuleService) {
            final var policyRuleService = (PolicyRuleService) policyRuleTypeSpecificType;
            final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();

            alarmDescriptorList = parsePolicyRuleCondition(policyRuleBasic);
            if (alarmDescriptorList.isEmpty()) {
                return List.of();
            }
        } else {
            final var policyRuleDevice = (PolicyRuleDevice) policyRuleTypeSpecificType;
            final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();

            alarmDescriptorList = parsePolicyRuleCondition(policyRuleBasic);
            if (alarmDescriptorList.isEmpty()) {
                return List.of();
            }
            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
                alarmPolicyRuleDeviceMap.put(alarmDescriptor.getAlarmId(), policyRuleDevice);
            }
        }

        return alarmDescriptorList;
    }

    private void monitorAlarmResponseForService(Multi<AlarmResponse> multi) {
        multi
                .subscribe()
                .with(
                        alarmResponse -> {
                            LOGGER.infof("**************************Received Alarm!**************************");
                            LOGGER.infof("alarmResponse:");
                            LOGGER.info(alarmResponse);
                            LOGGER.info(alarmResponse.getAlarmId());
                            applyActionService(alarmResponse.getAlarmId());
                        });
    }

    private void monitorAlarmResponseForDevice(Multi<AlarmResponse> multi) {
        multi
                .subscribe()
                .with(
                        alarmResponse -> {
                            LOGGER.infof("**************************Received Alarm!**************************");
                            LOGGER.infof("alarmResponse:");
                            LOGGER.info(alarmResponse);
                            LOGGER.info(alarmResponse.getAlarmId());
                            applyActionDevice(alarmResponse.getAlarmId());
                        });
    }

    // TODO: To be refactored or deprecated
    //    private void evaluateAction(
    //            PolicyRule policyRule,
    //            List<AlarmDescriptor> alarmDescriptorList,
    //            Multi<AlarmResponse> multi) {
    //
    //        Long count =
    //                multi
    //                        .collect()
    //                        .with(Collectors.counting())
    //                        .await()
    //                        .atMost(Duration.ofMinutes(POLICY_EVALUATION_TIMEOUT));
    //
    //        if (count > ACCEPTABLE_NUMBER_OF_ALARMS) {
    //            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
    //                monitoringService
    //                        .deleteAlarm(alarmDescriptor.getAlarmId())
    //                        .subscribe()
    //                        .with(
    //                                emptyMessage ->
    //                                        LOGGER.infof(
    //                                                "Alarm [%s] has been deleted as ineffective.\n",
    //                                                alarmDescriptor.getAlarmId()));
    //            }
    //            setPolicyRuleToContext(policyRule, INEFFECTIVE_POLICYRULE_STATE);
    //        } else {
    //            setPolicyRuleToContext(policyRule, EFFECTIVE_POLICYRULE_STATE);
    //        }
    //    }

    private void applyActionDevice(String alarmId) {
        PolicyRuleDevice policyRuleDevice = alarmPolicyRuleDeviceMap.get(alarmId);

        if (policyRuleActionMap.get(alarmId).getPolicyRuleActionEnum()
                == PolicyRuleActionEnum.POLICY_RULE_ACTION_SET_DEVICE_STATUS) {
            // In case additional PolicyRuleAction for Devices will be added
        }

        setPolicyRuleDeviceToContext(policyRuleDevice, ACTIVE_POLICYRULE_STATE);

        List<String> deviceIds = policyRuleDevice.getDeviceIds();
        List<PolicyRuleActionConfig> actionConfigs =
                policyRuleActionMap.get(alarmId).getPolicyRuleActionConfigs();

        if (deviceIds.size() != actionConfigs.size()) {
            String message =
                    String.format(
                            "The number of action parameters in PolicyRuleDevice with ID: %s, is not aligned with the number of devices.",
                            policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId());
            setPolicyRuleDeviceToContext(
                    policyRuleDevice, new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, message));
            return;
        }

        for (var i = 0; i < deviceIds.size(); i++) {
            activateDevice(deviceIds.get(i), actionConfigs.get(i));
        }

        setPolicyRuleDeviceToContext(policyRuleDevice, ENFORCED_POLICYRULE_STATE);
    }

    private void activateDevice(String deviceId, PolicyRuleActionConfig actionConfig) {

        Boolean toBeEnabled;
        if (actionConfig.getActionKey() == "ENABLED") {
            toBeEnabled = true;
        } else if (actionConfig.getActionKey() == "DISABLED") {
            toBeEnabled = false;
        } else {
            LOGGER.errorf(INVALID_MESSAGE, actionConfig.getActionKey());
            return;
        }

        final var deserializedDeviceUni = contextService.getDevice(deviceId);

        deserializedDeviceUni
                .subscribe()
                .with(
                        device -> {
                            if (toBeEnabled && device.isDisabled()) {
                                device.enableDevice();
                            } else if (!toBeEnabled && device.isEnabled()) {
                                device.disableDevice();
                            } else {
                                LOGGER.errorf(INVALID_MESSAGE, "Device is already in the desired state");
                                return;
                            }

                            deviceService.configureDevice(device);
                        });
    }

    private void addServiceConfigRule(
            PolicyRuleService policyRuleService, PolicyRuleAction policyRuleAction) {

        ConfigActionEnum configActionEnum = ConfigActionEnum.SET;
        List<PolicyRuleActionConfig> actionConfigs = policyRuleAction.getPolicyRuleActionConfigs();
        List<ConfigRule> newConfigRules = new ArrayList<>();

        for (PolicyRuleActionConfig actionConfig : actionConfigs) {
            ConfigRuleCustom configRuleCustom =
                    new ConfigRuleCustom(actionConfig.getActionKey(), actionConfig.getActionValue());
            ConfigRuleTypeCustom configRuleType = new ConfigRuleTypeCustom(configRuleCustom);
            ConfigRule configRule = new ConfigRule(configActionEnum, configRuleType);
            newConfigRules.add(configRule);
        }

        var deserializedServiceUni = contextService.getService(policyRuleService.getServiceId());
        deserializedServiceUni
                .subscribe()
                .with(
                        deserializedService -> {
                            List<ConfigRule> configRules =
                                    deserializedService.getServiceConfig().getConfigRules();
                            configRules.addAll(newConfigRules);
                            deserializedService.setServiceConfig(new ServiceConfig(configRules));
                        });
    }

    private void addServiceConstraint(
            PolicyRuleService policyRuleService, PolicyRuleAction policyRuleAction) {

        List<PolicyRuleActionConfig> actionConfigs = policyRuleAction.getPolicyRuleActionConfigs();
        List<Constraint> constraintList = new ArrayList<>();

        for (PolicyRuleActionConfig actionConfig : actionConfigs) {
            var constraintCustom =
                    new ConstraintCustom(actionConfig.getActionKey(), actionConfig.getActionValue());
            var constraintTypeCustom = new ConstraintTypeCustom(constraintCustom);
            constraintList.add(new Constraint(constraintTypeCustom));
        }

        final var deserializedServiceUni = contextService.getService(policyRuleService.getServiceId());

        deserializedServiceUni
                .subscribe()
                .with(
                        deserializedService -> {
                            deserializedService.appendServiceConstraints(constraintList);
                            serviceService.updateService(deserializedService);
                            setPolicyRuleServiceToContext(policyRuleService, ENFORCED_POLICYRULE_STATE);
                        });
    }

    private void callRecalculatePathRPC(
            PolicyRuleService policyRuleService, PolicyRuleAction policyRuleAction) {

        final var deserializedServiceUni = contextService.getService(policyRuleService.getServiceId());

        deserializedServiceUni
                .subscribe()
                .with(
                        deserializedService -> {
                            serviceService
                                    .recomputeConnections(deserializedService)
                                    .subscribe()
                                    .with(
                                            x -> {
                                                LOGGER.info("called recomputeConnections with:");
                                                LOGGER.info(deserializedService);
                                                setPolicyRuleServiceToContext(policyRuleService, ENFORCED_POLICYRULE_STATE);
                                            });
                        });
    }

    private void applyActionService(String alarmId) {
        PolicyRuleService policyRuleService = alarmPolicyRuleServiceMap.get(alarmId);
        PolicyRuleAction policyRuleAction =
                policyRuleService.getPolicyRuleBasic().getPolicyRuleActions().get(0);

        if (noAlarms == 0) {
            noAlarms++;
            setPolicyRuleServiceToContext(policyRuleService, ACTIVE_POLICYRULE_STATE);

            switch (policyRuleAction.getPolicyRuleActionEnum()) {
                case POLICY_RULE_ACTION_ADD_SERVICE_CONSTRAINT:
                    addServiceConstraint(policyRuleService, policyRuleAction);
                case POLICY_RULE_ACTION_ADD_SERVICE_CONFIGRULE:
                    addServiceConfigRule(policyRuleService, policyRuleAction);
                case POLICY_RULE_ACTION_RECALCULATE_PATH:
                    callRecalculatePathRPC(policyRuleService, policyRuleAction);
                default:
                    LOGGER.errorf(INVALID_MESSAGE, policyRuleAction.getPolicyRuleActionEnum());
                    return;
            }
        } else if (noAlarms == 2) {
            noAlarms = 0;
        } else {
            noAlarms++;
        }
    }

    private List<AlarmDescriptor> parsePolicyRuleCondition(PolicyRuleBasic policyRuleBasic) {
        BooleanOperator booleanOperator = policyRuleBasic.getBooleanOperator();
        if (booleanOperator == BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR) {
            return parsePolicyRuleConditionOr(policyRuleBasic);
        }
        if (booleanOperator == BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_AND) {
            return Arrays.asList(parsePolicyRuleConditionAnd(policyRuleBasic));
        }
        return List.of();
    }

    private List<AlarmDescriptor> parsePolicyRuleConditionOr(PolicyRuleBasic policyRuleBasic) {

        List<PolicyRuleCondition> policyRuleConditions = policyRuleBasic.getPolicyRuleConditions();
        List<AlarmDescriptor> alarmDescriptorList = new ArrayList<>();

        for (PolicyRuleCondition policyRuleCondition : policyRuleConditions) {
            var kpiValueRange = convertPolicyRuleConditionToKpiValueRange(policyRuleCondition);

            // TODO: Temp fix for AlarmDescriptor object
            AlarmDescriptor alarmDescriptor =
                    new AlarmDescriptor(
                            "alarmId-" + gen(),
                            "alarmDescription",
                            "alarmName-" + gen(),
                            policyRuleCondition.getKpiId(),
                            kpiValueRange,
                            getTimeStamp());

            alarmDescriptorList.add(alarmDescriptor);
        }

        HashMap<String, PolicyRuleAction> policyRuleActionMap = new HashMap<>();
        List<PolicyRuleAction> policyRuleActions = policyRuleBasic.getPolicyRuleActions();

        for (int i = 0; i < policyRuleActions.size(); i++) {
            policyRuleActionMap.put(alarmDescriptorList.get(i).getAlarmId(), policyRuleActions.get(i));
        }

        return alarmDescriptorList;
    }

    private AlarmDescriptor parsePolicyRuleConditionAnd(PolicyRuleBasic policyRuleBasic) {

        // TODO: KpiIds should be the same. Add check.

        List<PolicyRuleCondition> policyRuleConditionList = policyRuleBasic.getPolicyRuleConditions();
        List<String> kpisList = new ArrayList<String>();

        for (PolicyRuleCondition policyRuleCondition : policyRuleConditionList) {
            kpisList.add(policyRuleCondition.getKpiId());
        }

        if (policyRuleConditionList.size() > 1) {
            return createAlarmDescriptorWithRange(policyRuleConditionList);
        }

        return createAlarmDescriptorWithoutRange(policyRuleConditionList.get(0));
    }

    private AlarmDescriptor createAlarmDescriptorWithoutRange(
            PolicyRuleCondition policyRuleCondition) {

        final var kpiId = policyRuleCondition.getKpiId();
        final var kpiValueRange = convertPolicyRuleConditionToKpiValueRange(policyRuleCondition);

        return new AlarmDescriptor(
                "alarmId-" + gen(),
                "alarmDescription",
                "alarmName-" + gen(),
                kpiId,
                kpiValueRange,
                getTimeStamp());
    }

    private AlarmDescriptor createAlarmDescriptorWithRange(
            List<PolicyRuleCondition> policyRuleConditionList) {

        final var kpiId = policyRuleConditionList.get(0).getKpiId();

        HashMap<String, KpiValueRange> KpiValueRangeMap = new HashMap<>();
        for (PolicyRuleCondition policyRuleCondition : policyRuleConditionList) {

            if (!KpiValueRangeMap.containsKey(kpiId)) {
                var kpiValueRange = convertPolicyRuleConditionToKpiValueRange(policyRuleCondition);
                KpiValueRangeMap.put(kpiId, kpiValueRange);
                continue;
            }

            var kpiValueRange = convertPolicyRuleConditionToKpiValueRange(policyRuleCondition);
            // TODO: Handle combineKpiValueRanges exceptions
            var combinedKpiValueRange =
                    combineKpiValueRanges(kpiId, KpiValueRangeMap.get(kpiId), kpiValueRange);
            KpiValueRangeMap.put(kpiId, combinedKpiValueRange);
        }

        return new AlarmDescriptor(
                "alarmId-" + gen(),
                "alarmDescription",
                "alarmName-" + gen(),
                kpiId,
                KpiValueRangeMap.get(kpiId),
                getTimeStamp());
    }

    private KpiValueRange convertPolicyRuleConditionToKpiValueRange(
            PolicyRuleCondition policyRuleCondition) {

        switch (policyRuleCondition.getNumericalOperator()) {
            case POLICY_RULE_CONDITION_NUMERICAL_EQUAL:
                return new KpiValueRange(
                        policyRuleCondition.getKpiValue(), policyRuleCondition.getKpiValue(), true, true, true);
            case POLICY_RULE_CONDITION_NUMERICAL_NOT_EQUAL:
                return new KpiValueRange(
                        policyRuleCondition.getKpiValue(),
                        policyRuleCondition.getKpiValue(),
                        true,
                        false,
                        false);

            case POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN:
                return new KpiValueRange(null, policyRuleCondition.getKpiValue(), true, false, false);

            case POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN_EQUAL:
                return new KpiValueRange(null, policyRuleCondition.getKpiValue(), true, true, false);

            case POLICY_RULE_CONDITION_NUMERICAL_LESS_THAN:
                return new KpiValueRange(policyRuleCondition.getKpiValue(), null, true, false, false);

            case POLICY_RULE_CONDITION_NUMERICAL_LESS_THAN_EQUAL:
                return new KpiValueRange(policyRuleCondition.getKpiValue(), null, true, false, true);
            default:
                return null;
        }
    }

    private KpiValueRange combineKpiValueRanges(
            String kpiId, KpiValueRange firstKpiValueRange, KpiValueRange secondKpiValueRange) {
        if (secondKpiValueRange.getInRange() == true) {
            LOGGER.errorf("KpiId: %s, has already range values", kpiId);
            return null;
        }

        if ((firstKpiValueRange.getKpiMinValue() != null)
                && (secondKpiValueRange.getKpiMinValue() != null)) {
            LOGGER.errorf("KpiId: %s, has already min value", kpiId);
            return null;
        }

        if ((firstKpiValueRange.getKpiMaxValue() != null)
                && (secondKpiValueRange.getKpiMinValue() != null)) {
            LOGGER.errorf("KpiId: %s, has already max value", kpiId);
            return null;
        }

        // Objects.nonNull(secondKpiValueRange);

        var kpiMinValue =
                firstKpiValueRange.getKpiMinValue() != null
                        ? firstKpiValueRange.getKpiMinValue()
                        : secondKpiValueRange.getKpiMinValue();
        var kpiMaxValue =
                firstKpiValueRange.getKpiMaxValue() != null
                        ? firstKpiValueRange.getKpiMaxValue()
                        : secondKpiValueRange.getKpiMaxValue();
        boolean includeMinValue =
                firstKpiValueRange.getIncludeMinValue() || secondKpiValueRange.getIncludeMinValue();
        boolean includeMaxValue =
                firstKpiValueRange.getIncludeMaxValue() || secondKpiValueRange.getIncludeMaxValue();

        return new KpiValueRange(kpiMinValue, kpiMaxValue, true, includeMinValue, includeMaxValue);
    }

    private void setPolicyRuleToContext(PolicyRule policyRule, PolicyRuleState policyRuleState) {
        final var policyRuleType = policyRule.getPolicyRuleType();
        final var policyRuleTypeSpecificType = policyRuleType.getPolicyRuleType();

        if (policyRuleTypeSpecificType instanceof PolicyRuleService) {
            setPolicyRuleServiceToContext(
                    (PolicyRuleService) policyRuleTypeSpecificType, policyRuleState);
        }
        if (policyRuleTypeSpecificType instanceof PolicyRuleDevice) {
            setPolicyRuleDeviceToContext((PolicyRuleDevice) policyRuleTypeSpecificType, policyRuleState);
        }
    }

    private void setPolicyRuleServiceToContext(
            PolicyRuleService policyRuleService, PolicyRuleState policyRuleState) {
        LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(policyRuleState);
        policyRuleService.setPolicyRuleBasic(policyRuleBasic);

        final var policyRuleTypeService = new PolicyRuleTypeService(policyRuleService);
        final var policyRule = new PolicyRule(policyRuleTypeService);
        contextService.setPolicyRule(policyRule);
    }

    private void setPolicyRuleDeviceToContext(
            PolicyRuleDevice policyRuleDevice, PolicyRuleState policyRuleState) {
        LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(policyRuleState);
        policyRuleDevice.setPolicyRuleBasic(policyRuleBasic);

        final var policyRuleTypeService = new PolicyRuleTypeDevice(policyRuleDevice);
        final var policyRule = new PolicyRule(policyRuleTypeService);
        contextService.setPolicyRule(policyRule);
    }
}
