/*
* 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.model.PolicyRuleBasic;
import eu.teraflow.policy.model.PolicyRuleDevice;
import eu.teraflow.policy.model.PolicyRuleService;
import eu.teraflow.policy.model.PolicyRuleState;
import eu.teraflow.policy.model.RuleState;
import eu.teraflow.policy.monitoring.MonitoringService;
import eu.teraflow.policy.service.ServiceService;
import io.smallrye.mutiny.Uni;
import java.util.ArrayList;
import java.util.List;
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(RuleState.POLICY_INSERTED);
    private static final PolicyRuleState VALIDATED_POLICYRULE_STATE =
            new PolicyRuleState(RuleState.POLICY_VALIDATED);
    private static final PolicyRuleState FAILED_POLICYRULE_STATE =
            new PolicyRuleState(RuleState.POLICY_FAILED);

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

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

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

        final var serviceId = policyRuleService.getServiceId();
        final var deviceIds = policyRuleService.getDeviceIds();
        final var policyRuleBasic = policyRuleService.getPolicyRuleBasic();

        final var policyRuleConditions = policyRuleBasic.getPolicyRuleConditions();
        final var kpiIds = policyRuleConditionFieldsGetter.getKpiIds(policyRuleConditions);
        final var kpiValues = policyRuleConditionFieldsGetter.getKpiValues(policyRuleConditions);
        final var numericalOperators =
                policyRuleConditionFieldsGetter.getNumericalOperators(policyRuleConditions);

        final var isServiceIdValid = policyRuleConditionValidator.validateServiceId(serviceId);

        logAndSetPolicyRuleState(INSERTED_POLICYRULE_STATE, policyRuleBasic);
        contextService.setPolicyRule(policyRuleBasic);

        // VALIDATION PHASE
        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.");
                                }

                                logAndSetPolicyRuleState(FAILED_POLICYRULE_STATE, policyRuleBasic);
                            } else {
                                LOGGER.infof(VALID_MESSAGE, serviceId);

                                final var invalidDeviceIds = returnInvalidDeviceIds(deviceIds);

                                if (!invalidDeviceIds.isEmpty()) {
                                    logAndSetPolicyRuleState(FAILED_POLICYRULE_STATE, policyRuleBasic);
                                    contextService.setPolicyRule(policyRuleBasic);
                                } else {
                                    LOGGER.infof("All deviceIds are valid");
                                }

                                logAndSetPolicyRuleState(VALIDATED_POLICYRULE_STATE, policyRuleBasic);
                            }

                            contextService.setPolicyRule(policyRuleBasic);
                        });

        // PROVISION PHASE

        final var policyRuleState = new PolicyRuleState(RuleState.POLICY_VALIDATED);

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

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

        final var policyRuleBasic = policyRuleDevice.getPolicyRuleBasic();
        final var deviceIds = policyRuleDevice.getDeviceIds();

        final var policyRuleConditions = policyRuleBasic.getPolicyRuleConditions();
        final var kpiIds = policyRuleConditionFieldsGetter.getKpiIds(policyRuleConditions);
        final var kpiValues = policyRuleConditionFieldsGetter.getKpiValues(policyRuleConditions);
        final var numericalOperators =
                policyRuleConditionFieldsGetter.getNumericalOperators(policyRuleConditions);

        logAndSetPolicyRuleState(INSERTED_POLICYRULE_STATE, policyRuleBasic);
        contextService.setPolicyRule(policyRuleBasic);

        // VALIDATION PHASE
        final var invalidDeviceIds = returnInvalidDeviceIds(deviceIds);

        if (!invalidDeviceIds.isEmpty()) {
            logAndSetPolicyRuleState(FAILED_POLICYRULE_STATE, policyRuleBasic);
        } else {
            LOGGER.infof("All deviceIds are valid");
            logAndSetPolicyRuleState(VALIDATED_POLICYRULE_STATE, policyRuleBasic);
        }
        contextService.setPolicyRule(policyRuleBasic);

        // PROVISION PHASE

        final var policyRuleState = new PolicyRuleState(RuleState.POLICY_VALIDATED);

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

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

    private void logAndSetPolicyRuleState(
            PolicyRuleState policyRuleState, PolicyRuleBasic policyRuleBasic) {
        final var POLICY_RULE_STATE_MESSAGE = "Setting Policy Rule state to [%s]";

        if (policyRuleState.getRuleState() == RuleState.POLICY_INSERTED) {
            LOGGER.infof(POLICY_RULE_STATE_MESSAGE, RuleState.POLICY_INSERTED.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
        }

        if (policyRuleState.getRuleState() == RuleState.POLICY_VALIDATED) {
            LOGGER.infof(POLICY_RULE_STATE_MESSAGE, RuleState.POLICY_VALIDATED.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
        }

        if (policyRuleState.getRuleState() == RuleState.POLICY_FAILED) {
            LOGGER.errorf(POLICY_RULE_STATE_MESSAGE, RuleState.POLICY_FAILED.toString());
            policyRuleBasic.setPolicyRuleState(policyRuleState);
        }
    }
}
