package com.capillary.shopbook.pointsengine.RESTEndpoint.validators.impl;

import com.capillary.shopbook.pointsengine.RESTEndpoint.models.*;
import com.capillary.shopbook.pointsengine.RESTEndpoint.validators.Interfaces.PromotionValidator;
import com.capillary.shopbook.pointsengine.RESTEndpoint.validators.models.PromotionValidatorRequest;
import com.capillary.shopbook.pointsengine.RESTEndpoint.validators.models.ResponseCodes;
import com.capillary.shopbook.pointsengine.RESTEndpoint.validators.models.ResponseMessage;
import com.capillary.shopbook.pointsengine.RESTEndpoint.validators.models.ValidatorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class PromotionRulesetsValidator implements PromotionValidator {
    private static final Logger log = LoggerFactory.getLogger(PromotionRulesetsValidator.class);
    private static final String TARGET_GROUP_ID = "TargetGroupId";
    private static final String TARGET_RULE_ID = "TargetRuleId";

    @Override
    public ValidatorResponse validate(PromotionValidatorRequest request) {
        ValidatorResponse response = new ValidatorResponse();
        Promotion promotion = request.getInputData();
        int rulesetInfoSize = request.getInputData().getRulesetInfos().size();
        log.info("Ruleset Size is : {}", rulesetInfoSize);

        // Check if more than one Ruleset is passed
        if (rulesetInfoSize != 1) {
            response.setResponseMessage(ResponseMessage.UCC_PROMOTION_RULESET_LIMIT_EXCEEDED);
            response.setResponseCode(ResponseCodes.UCC_PROMOTION_RULESET_LIMIT_EXCEEDED.getResponseCode());
            response.setStatus(false);
            return response;
        }

        // Check the required fields in the RuleSet
        RulesetInfo rulesetInfo = request.getInputData().getRulesetInfos().get(0);
        if (!validateRulesetInfoFields(rulesetInfo)) {
            response.setResponseMessage(ResponseMessage.INVALID_UCC_PROMOTION_RULESETS);
            response.setResponseCode(ResponseCodes.INVALID_UCC_PROMOTION_RULESETS.getResponseCode());
            response.setStatus(false);
            return response;
        }

        if (!validateRules(rulesetInfo.getRules(), promotion)) {
            response.setResponseMessage(ResponseMessage.INVALID_UCC_PROMOTION_RULES);
            response.setResponseCode(ResponseCodes.INVALID_UCC_PROMOTION_RULESETS.getResponseCode());
            response.setStatus(false);
            return response;
        }
        if (!CollectionUtils.isEmpty(rulesetInfo.getFilterInfo())) {
            if (validateRuleSetFilters(rulesetInfo.getFilterInfo())) {
                response.setResponseMessage(ResponseMessage.INVALID_UCC_PROMOTION_RULESET_FILTERS);
                response.setResponseCode(ResponseCodes.INVALID_UCC_PROMOTION_RULESET_FILTERS.getResponseCode());
                response.setStatus(false);
                return response;
            }
        }

        response.setStatus(true);
        response.setResponseCode(200);
        return response;
    }

    private boolean validateRulesetInfoFields(RulesetInfo rulesetInfo) {
        if (rulesetInfo.getOrgID() == null) {
            log.error("OrgID cannot be null in Ruleset.");
            return false;
        }
        if (rulesetInfo.getContextID() == null) {
            log.error("ContextId cannot be null in Ruleset.");
            return false;
        }
        if (rulesetInfo.getName() == null) {
            log.error("Name cannot be null in Ruleset.");
            return false;
        }
        if (rulesetInfo.getContextType() == null) {
            log.error("ContextType cannot be null in Ruleset.");
            return false;
        }
        if (rulesetInfo.getStartDate() == null || rulesetInfo.getEndDate() == null) {
            log.error("Start date and end date cannot be null.");
            return false;
        }
        if (CollectionUtils.isEmpty(rulesetInfo.getRules())) {
            log.error("Rules list cannot be empty for Ruleset.");
            return false;
        }
        return true;
    }

    private boolean validateRules(List<RuleInfo> ruleInfo, Promotion promotion) {
        for (RuleInfo rules : ruleInfo) {
            if (rules.getExp() == null) {
                log.error("Expression cannot be null for a Rule.");
                return false;
            }
            if (rules.getExpJSON() == null) {
                log.error("ExpJSON cannot be null for a Rule.");
                return false;
            }
            if (rules.getName() == null) {
                log.error("Rule Name cannot be null for a Rule.");
                return false;
            }
            if (!validateCaseToActions(rules.getCaseToActions(), promotion)) {
                return false;
            }
            if (!CollectionUtils.isEmpty(rules.getFilterInfo())) {
                if (validateRuleSetFilters(rules.getFilterInfo())) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean validateCaseToActions(Map<String, List<ActionInfo>> caseToActions, Promotion promotion) {
        for (Map.Entry<String, List<ActionInfo>> entry : caseToActions.entrySet()) {
            if (!validateCaseActionListInfo(entry, promotion)) {
                return false;
            }
        }
        return true;
    }

    private boolean validateCaseActionListInfo(Map.Entry<String, List<ActionInfo>> entry, Promotion promotion) {
        for (ActionInfo actionInfo : entry.getValue()) {
            if (actionInfo.getActionName() == null) {
                log.error("Action Name cannot be null.");
                return false;
            }
            if (CollectionUtils.isEmpty(actionInfo.getMandatoryPropertiesValues())) {
                log.error("Action Mandatory Properties cannot be empty.");
                return false;
            } else if (!validateSimpleProperties(actionInfo.getMandatoryPropertiesValues(), promotion)) {
                return false;
            }
        }
        return true;
    }

    private boolean validateSimpleProperties(List<MandatoryProperty> simplePropertyMaps, Promotion promotion) {
        boolean isValid = true;

        // Create a map from the list of properties for quick lookup
        Map<String, String> propertyMap = simplePropertyMaps.stream()
                .collect(Collectors.toMap(MandatoryProperty::getKey, MandatoryProperty::getValue));

        // Check for null keys and values
        for (Map.Entry<String, String> entry : propertyMap.entrySet()) {
            if (entry.getKey() == null) {
                log.error("Simple property key cannot be null.");
                isValid = false;
            }
            if (entry.getValue() == null) {
                log.error("Simple property value cannot be null.");
                isValid = false;
            }
        }
        // Ensure TargetGroupId and TargetRuleId is present in simple properties always
        if (!propertyMap.containsKey(TARGET_GROUP_ID)) {
            log.info("Adding missing key: {}", TARGET_GROUP_ID);
            propertyMap.put(TARGET_GROUP_ID, promotion.getLoyaltyConfigMetaData().getTargetGroupId().toString());
        }
        if (!propertyMap.containsKey(TARGET_RULE_ID)) {
            log.info("Adding missing key: {}", TARGET_RULE_ID);
            propertyMap.put(TARGET_RULE_ID, promotion.getLoyaltyConfigMetaData().getTargetRuleId().toString());
        }
        // Update the original list with the updated map
        simplePropertyMaps.clear();
        simplePropertyMaps.addAll(propertyMap.entrySet().stream()
                .map(entry -> {
                    MandatoryProperty property = new MandatoryProperty();
                    property.setKey(entry.getKey());
                    property.setValue(entry.getValue());
                    return property;
                })
                .collect(Collectors.toList()));

        return isValid;
    }

    private boolean validateRuleSetFilters(List<FilterInfo> filters) {
        Map<String, String> filterClassMapping = FilterInfo.getFilterClassMapping();
        for (FilterInfo filterInfo : filters) {
            String filterName = filterInfo.getName();
            if (!filterClassMapping.containsKey(filterName)) {
                log.error("Invalid Name passed in filter");
                return true;
            }
        }
        return false;
    }
}


write UT's for condition and line coverage for this class