/*
* 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.
*/

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.NumericalOperator;
import eu.teraflow.policy.model.PolicyRuleAction;
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.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 java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
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 PolicyRuleState INSERTED_POLICYRULE_STATE =
            new PolicyRuleState(
                    PolicyRuleStateEnum.POLICY_INSERTED, "Successfully set 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 set to UPDATED STATE");
    private static final PolicyRuleState REMOVED_POLICYRULE_STATE =
            new PolicyRuleState(PolicyRuleStateEnum.POLICY_REMOVED, "Successfully set 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 PolicyRuleState createFailedPolicyRuleState(String message) {
        return new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, message);
    }

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

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(INSERTED_POLICYRULE_STATE);

        contextService
                .setPolicyRule(policyRuleBasic)
                .subscribe()
                .with(id -> validateService(policyRuleService));

        return Uni.createFrom().item(policyRuleBasic.getPolicyRuleState());
    }

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

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(UPDATED_POLICYRULE_STATE);

        contextService
                .setPolicyRule(policyRuleBasic)
                .subscribe()
                .with(id -> validateUpdatedPolicyService(policyRuleService));

        return Uni.createFrom().item(policyRuleBasic.getPolicyRuleState());
    }

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

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(INSERTED_POLICYRULE_STATE);

        contextService
                .setPolicyRule(policyRuleBasic)
                .subscribe()
                .with(id -> validateDevice(policyRuleDevice));
        return Uni.createFrom().item(policyRuleBasic.getPolicyRuleState());
    }

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

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        policyRuleBasic.setPolicyRuleState(UPDATED_POLICYRULE_STATE);

        contextService
                .setPolicyRule(policyRuleBasic)
                .subscribe()
                .with(id -> validateUpdatedPolicyDevice(policyRuleDevice));

        return Uni.createFrom().item(policyRuleBasic.getPolicyRuleState());
    }

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

        // TODO: Specify timeout in case of connectivity issue
        PolicyRuleBasic policyRuleBasic =
                contextService.getPolicyRule(policyRuleId).await().indefinitely();
        List<PolicyRuleCondition> policyRuleConditions = policyRuleBasic.getPolicyRuleConditions();

        for (PolicyRuleCondition policy : policyRuleConditions) {
            var empty = monitoringService.deleteKpi(policy.getKpiId());
            empty
                    .subscribe()
                    .with(emptyMessage -> LOGGER.infof("Kpi [%s] has been deleted.\n", policyRuleId));
        }

        var empty = contextService.removePolicyRule(policyRuleId);
        empty
                .subscribe()
                .with(emptyMessage -> LOGGER.infof("Policy [%s] has been removed.\n", policyRuleId));

        policyRuleBasic.setPolicyRuleState(REMOVED_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        return Uni.createFrom().item(policyRuleBasic.getPolicyRuleState());
    }

    private void provisionAlarm(
            PolicyRuleBasic policyRuleBasic,
            List<AlarmDescriptor> alarmDescriptorList,
            Boolean isService) {

        List<AlarmSubscription> alarmSubscriptionList = new ArrayList<>();

        for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
            monitoringService
                    .setKpiAlarm(alarmDescriptor)
                    .subscribe()
                    .with(alarmId -> alarmSubscriptionList.add(new AlarmSubscription(alarmId, 0, 0)));
        }

        LOGGER.infof("Setting Policy Rule state to [%s]", PROVISIONED_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(PROVISIONED_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
        for (AlarmSubscription alarmSubscription : alarmSubscriptionList) {
            alarmResponseStreamList.add(monitoringService.getAlarmResponseStream(alarmSubscription));
        }
        Multi<AlarmResponse> multi = Multi.createBy().merging().streams(alarmResponseStreamList);

        multi
                .select()
                .first()
                .subscribe()
                .with(
                        alarmResponse -> {
                            LOGGER.info(alarmResponse);
                            if (isService) {
                                applyActionService(alarmResponse.getAlarmId());
                            } else {
                                applyActionDevice(alarmResponse.getAlarmId());
                            }
                        });

        Long count = multi.collect().with(Collectors.counting()).await().atMost(Duration.ofMinutes(5));
        if (count > 3) {
            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
                monitoringService
                        .deleteAlarm(alarmDescriptor.getAlarmId())
                        .subscribe()
                        .with(
                                emptyMessage ->
                                        LOGGER.infof("Alarm [%s] has been deleted.\n", alarmDescriptor.getAlarmId()));
            }

            LOGGER.infof("Setting Policy Rule state to [%s]", INEFFECTIVE_POLICYRULE_STATE.toString());
            policyRuleBasic.setPolicyRuleState(INEFFECTIVE_POLICYRULE_STATE);
            contextService.setPolicyRule(policyRuleBasic);
        } else {
            LOGGER.infof("Setting Policy Rule state to [%s]", EFFECTIVE_POLICYRULE_STATE.toString());
            policyRuleBasic.setPolicyRuleState(EFFECTIVE_POLICYRULE_STATE);
            contextService.setPolicyRule(policyRuleBasic);

            multi
                    .subscribe()
                    .with(
                            alarmResponse -> {
                                LOGGER.info(alarmResponse);
                                if (isService) {
                                    applyActionService(alarmResponse.getAlarmId());
                                } else {
                                    applyActionDevice(alarmResponse.getAlarmId());
                                }
                            });
        }
    }

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

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

        LOGGER.infof("Setting Policy Rule state to [%s]", ACTIVE_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(ACTIVE_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        List<String> deviceIds = policyRuleDevice.getDeviceIds();
        List<String> actionParameters =
                policyRuleActionMap.get(alarmId).getPolicyRuleActionParameters();

        if (deviceIds.size() != 3 * actionParameters.size()) {
            LOGGER.errorf(
                    INVALID_MESSAGE,
                    "The number of action parameters are not aligned with the number of devices");
            return;
        }

        for (var i = 0; i < deviceIds.size(); i++) {
            List<String> deviceActions = actionParameters.subList(i, i + 2);
            activateDevice(deviceIds.get(i), deviceActions);
        }

        LOGGER.infof("Setting Policy Rule state to [%s]", ENFORCED_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(ENFORCED_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);
    }

    private void activateDevice(String deviceId, List<String> actionParameters) {

        Boolean toBeEnabled;
        if (actionParameters.get(0) == "ENABLED") {
            toBeEnabled = true;
        } else if (actionParameters.get(0) == "DISABLED") {
            toBeEnabled = false;
        } else {
            LOGGER.errorf(INVALID_MESSAGE, actionParameters.get(0));
            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) {

        List<String> actionParameters = policyRuleAction.getPolicyRuleActionParameters();
        List<ConfigRule> newConfigRules = new ArrayList<>();

        // ConfigeRuleTypeCustom has 3 elements
        for (int i = 0; i < actionParameters.size() - 3; i += 3) {
            ConfigActionEnum configActionEnum;
            if (actionParameters.get(i) == "SET") {
                configActionEnum = ConfigActionEnum.SET;
            } else if (actionParameters.get(0) == "DELETE") {
                configActionEnum = ConfigActionEnum.DELETE;
            } else {
                configActionEnum = ConfigActionEnum.UNDEFINED;
                break;
            }

            ConfigRuleCustom configRuleCustom =
                    new ConfigRuleCustom(actionParameters.get(i + 1), actionParameters.get(i + 2));
            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) {

        PolicyRuleBasic policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        List<String> actionParameters = policyRuleAction.getPolicyRuleActionParameters();
        List<Constraint> constraintList = new ArrayList<>();

        for (int i = 0; i < actionParameters.size() - 2; i += 2) {
            var constraintCustom =
                    new ConstraintCustom(actionParameters.get(i), actionParameters.get(i + 1));
            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);

                            LOGGER.infof(
                                    "Setting Policy Rule state to [%s]", ENFORCED_POLICYRULE_STATE.toString());
                            policyRuleBasic.setPolicyRuleState(ENFORCED_POLICYRULE_STATE);
                            contextService.setPolicyRule(policyRuleBasic);
                        });
    }

    private void applyActionService(String alarmId) {
        PolicyRuleService policyRuleService = alarmPolicyRuleServiceMap.get(alarmId);
        PolicyRuleBasic policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        PolicyRuleAction policyRuleAction = policyRuleActionMap.get(alarmId);

        LOGGER.infof("Setting Policy Rule state to [%s]", ACTIVE_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(ACTIVE_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        switch (policyRuleAction.getPolicyRuleActionEnum()) {
            case POLICY_RULE_ACTION_ADD_SERVICE_CONSTRAINT:
                addServiceConstraint(policyRuleService, policyRuleAction);
            case POLICY_RULE_ACTION_ADD_SERVICE_CONFIGRULE:
                addServiceConfigRule(policyRuleService, policyRuleAction);
            default:
                LOGGER.errorf(INVALID_MESSAGE, policyRuleAction.getPolicyRuleActionEnum());
                return;
        }
    }

    private void validateDevice(PolicyRuleDevice policyRuleDevice) {
        final var deviceIds = policyRuleDevice.getDeviceIds();
        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        final var policyRuleId = policyRuleBasic.getPolicyRuleId();

        final var invalidDeviceIds = returnInvalidDeviceIds(deviceIds);
        if (!invalidDeviceIds.isEmpty()) {
            var policyRuleState =
                    createFailedPolicyRuleState(
                            "The Devices of PolicyRuleDevice " + policyRuleId + " are not valid");
            LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
            contextService.setPolicyRule(policyRuleBasic);
            return;
        }

        List<AlarmDescriptor> alarmDescriptorList =
                parsePolicyRuleCondition(policyRuleDevice.getPolicyRuleBasic());

        if (alarmDescriptorList.isEmpty()) {
            var policyRuleState =
                    createFailedPolicyRuleState(
                            "The PolicyRuleConditions of PolicyRuleDevice " + policyRuleId + " are not valid");
            LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
            contextService.setPolicyRule(policyRuleBasic);
            return;
        }

        LOGGER.infof("Setting Policy Rule state to [%s]", VALIDATED_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(VALIDATED_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        provisionAlarm(policyRuleBasic, alarmDescriptorList, false);
        return;
    }

    private void validateUpdatedPolicyService(PolicyRuleService policyRuleService) {

        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();
        final var isUpdatedPolicyRuleValid =
                policyRuleConditionValidator.validateUpdatedPolicyRuleId(policyRuleBasic.getPolicyRuleId());

        isUpdatedPolicyRuleValid
                .subscribe()
                .with(
                        policyRuleBoolean -> {
                            if (Boolean.FALSE.equals(isUpdatedPolicyRuleValid)) {
                                var policyRuleState =
                                        createFailedPolicyRuleState(
                                                "The requested PolicyRuleService to update: "
                                                        + policyRuleBasic.getPolicyRuleId()
                                                        + " is not valid");
                                LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
                                policyRuleBasic.setPolicyRuleState(policyRuleState);
                                contextService.setPolicyRule(policyRuleBasic);
                                return;
                            }

                            validateService(policyRuleService);
                        });
    }

    private void validateUpdatedPolicyDevice(PolicyRuleDevice policyRuleDevice) {

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        final var isUpdatedPolicyRuleValid =
                policyRuleConditionValidator.validateUpdatedPolicyRuleId(policyRuleBasic.getPolicyRuleId());

        isUpdatedPolicyRuleValid
                .subscribe()
                .with(
                        policyRuleBoolean -> {
                            if (Boolean.FALSE.equals(isUpdatedPolicyRuleValid)) {
                                var policyRuleState =
                                        createFailedPolicyRuleState(
                                                "The requested PolicyRuleDevice to update: "
                                                        + policyRuleBasic.getPolicyRuleId()
                                                        + " is not valid");
                                LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
                                policyRuleBasic.setPolicyRuleState(policyRuleState);
                                contextService.setPolicyRule(policyRuleBasic);
                                return;
                            }
                            validateDevice(policyRuleDevice);
                        });
    }

    private void validateService(PolicyRuleService policyRuleService) {
        final var serviceId = policyRuleService.getServiceId();
        final var deviceIds = policyRuleService.getDeviceIds();
        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();

        final var isServiceIdValid = policyRuleConditionValidator.validateServiceId(serviceId);

        isServiceIdValid
                .subscribe()
                .with(
                        serviceIdBooleanValue -> {
                            if (Boolean.FALSE.equals(serviceIdBooleanValue)) {
                                LOGGER.errorf(INVALID_MESSAGE, serviceId);
                                final var invalidDeviceIds = returnInvalidDeviceIds(deviceIds);

                                if (invalidDeviceIds.isEmpty()) {
                                    LOGGER.info("All Device Ids are valid.");
                                }
                                var policyRuleState =
                                        createFailedPolicyRuleState(
                                                "The Service of PolicyRuleService "
                                                        + policyRuleBasic.getPolicyRuleId()
                                                        + " is not valid");
                                LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
                                policyRuleBasic.setPolicyRuleState(policyRuleState);
                                contextService.setPolicyRule(policyRuleBasic);
                                return;
                            } else {
                                LOGGER.infof(VALID_MESSAGE, serviceId);

                                final var invalidDeviceIds = returnInvalidDeviceIds(deviceIds);

                                if (!invalidDeviceIds.isEmpty()) {
                                    var policyRuleState =
                                            createFailedPolicyRuleState(
                                                    "The Devices of PolicyRuleService "
                                                            + policyRuleBasic.getPolicyRuleId()
                                                            + " are not valid");
                                    LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
                                    policyRuleBasic.setPolicyRuleState(policyRuleState);
                                    contextService.setPolicyRule(policyRuleBasic);
                                    return;
                                } else {
                                    LOGGER.infof("All deviceIds are valid");
                                }

                                policyRuleBasic.setPolicyRuleState(VALIDATED_POLICYRULE_STATE);
                                contextService.setPolicyRule(policyRuleBasic);
                                return;
                            }
                        });

        if (policyRuleBasic.getPolicyRuleState() != VALIDATED_POLICYRULE_STATE) {
            return;
        }

        List<AlarmDescriptor> alarmDescriptorList =
                parsePolicyRuleCondition(policyRuleService.getPolicyRuleBasic());

        if (alarmDescriptorList.isEmpty()) {
            var policyRuleState =
                    createFailedPolicyRuleState(
                            "The PolicyRuleConditions of PolicyRuleDevice "
                                    + policyRuleBasic.getPolicyRuleId()
                                    + " are not valid");
            LOGGER.infof("Setting Policy Rule state to [%s]", policyRuleState.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
            contextService.setPolicyRule(policyRuleBasic);
            return;
        }
        LOGGER.infof("Setting Policy Rule state to [%s]", VALIDATED_POLICYRULE_STATE.toString());
        policyRuleBasic.setPolicyRuleState(VALIDATED_POLICYRULE_STATE);
        contextService.setPolicyRule(policyRuleBasic);

        for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
            alarmPolicyRuleServiceMap.put(alarmDescriptor.getAlarmId(), policyRuleService);
        }

        provisionAlarm(policyRuleBasic, alarmDescriptorList, true);
        return;
    }

    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 kpiIdList = Arrays.asList(policyRuleCondition.getKpiId());
            var kpiValueRange = convertPolicyRuleConditionToAlarmDescriptor(policyRuleCondition);
            var kpiValueRangeList = Arrays.asList(kpiValueRange);

            AlarmDescriptor alarmDescriptor =
                    new AlarmDescriptor(
                            "alarmId-" + gen(),
                            "alarmDescription",
                            "alarmName-" + gen(),
                            kpiIdList,
                            kpiValueRangeList,
                            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 KpiValueRange convertPolicyRuleConditionToAlarmDescriptor(
            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(policyRuleCondition.getKpiValue(), null, false, false, false);

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

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

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

    private AlarmDescriptor parsePolicyRuleConditionAnd(PolicyRuleBasic policyRuleBasic) {

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

        for (PolicyRuleCondition policyRuleCondition : policyRuleConditionList) {
            kpisList.add(policyRuleCondition.getKpiId());
        }
        Set<String> kpisSet = new HashSet<String>(kpisList);

        if (kpisSet.size() == kpisList.size()) {
            List<String> kpiIdList = new ArrayList<>();
            List<KpiValueRange> kpiValueRangeList = new ArrayList<>();
            for (PolicyRuleCondition policyRuleCondition : policyRuleConditionList) {

                kpisList.add(policyRuleCondition.getKpiId());
                kpiValueRangeList.add(convertPolicyRuleConditionToAlarmDescriptor(policyRuleCondition));
            }

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

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

            if (KpiValueRangeMap.containsKey(policyRuleCondition.getKpiId())) {
                var kpiValueRange = KpiValueRangeMap.get(policyRuleCondition.getKpiId());

                if (kpiValueRange.getInRange() == true) {
                    LOGGER.errorf("Received %s", policyRuleBasic);
                    return null;
                }

                if ((kpiValueRange.getKpiMaxValue() != null) && (kpiValueRange.getKpiMinValue() != null)) {
                    LOGGER.errorf("Received %s", policyRuleBasic);
                    return null;
                }

                if (policyRuleCondition.getNumericalOperator()
                                == NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN
                        && kpiValueRange.getKpiMinValue() == null) {
                    KpiValueRangeMap.put(
                            policyRuleCondition.getKpiId(),
                            new KpiValueRange(
                                    policyRuleCondition.getKpiValue(),
                                    kpiValueRange.getKpiMaxValue(),
                                    true,
                                    false,
                                    kpiValueRange.getIncludeMaxValue()));
                }

                if (policyRuleCondition.getNumericalOperator()
                                == NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN_EQUAL
                        && kpiValueRange.getKpiMinValue() == null) {
                    KpiValueRangeMap.put(
                            policyRuleCondition.getKpiId(),
                            new KpiValueRange(
                                    policyRuleCondition.getKpiValue(),
                                    kpiValueRange.getKpiMaxValue(),
                                    true,
                                    true,
                                    kpiValueRange.getIncludeMaxValue()));
                }

                if (policyRuleCondition.getNumericalOperator()
                                == NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_LESS_THAN
                        && kpiValueRange.getKpiMaxValue() == null) {
                    KpiValueRangeMap.put(
                            policyRuleCondition.getKpiId(),
                            new KpiValueRange(
                                    kpiValueRange.getKpiMinValue(),
                                    policyRuleCondition.getKpiValue(),
                                    true,
                                    kpiValueRange.getIncludeMinValue(),
                                    false));
                }

                if (policyRuleCondition.getNumericalOperator()
                                == NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_LESS_THAN_EQUAL
                        && kpiValueRange.getKpiMaxValue() == null) {
                    KpiValueRangeMap.put(
                            policyRuleCondition.getKpiId(),
                            new KpiValueRange(
                                    kpiValueRange.getKpiMinValue(),
                                    policyRuleCondition.getKpiValue(),
                                    true,
                                    kpiValueRange.getIncludeMinValue(),
                                    true));
                }
            }
        }

        List<String> kpiIdList = new ArrayList<>();
        kpiIdList.addAll(KpiValueRangeMap.keySet());
        List<KpiValueRange> kpiValueRangeList = new ArrayList<>(KpiValueRangeMap.values());

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

    private List<String> returnInvalidDeviceIds(List<String> deviceIds) {
        var invalidDeviceIds = new ArrayList<String>();

        if (!deviceIds.isEmpty()) {

            for (String deviceId : deviceIds) {
                final var validatedDeviceId = policyRuleConditionValidator.validateDeviceId(deviceId);

                validatedDeviceId
                        .subscribe()
                        .with(
                                deviceIdBoolean -> {
                                    if (Boolean.FALSE.equals(deviceIdBoolean)) {
                                        invalidDeviceIds.add(deviceId);
                                    }
                                });
            }

        } else {
            LOGGER.warnf("No deviceIds found");
        }

        return invalidDeviceIds;
    }
}
