package ru.yandex.qe.dispenser.ws.quota.request.workflow.service;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;

import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.exception.SingleMessageException;
import ru.yandex.qe.dispenser.ws.quota.request.workflow.context.RequestContext;

import static org.apache.commons.lang3.StringUtils.trimToEmpty;

@ParametersAreNonnullByDefault
public class SimpleServicePropertyValidator implements ServicePropertyValidator {

    private final Set<String> requiredProperties;
    private final Set<String> optionalProperties;

    public SimpleServicePropertyValidator(final Set<String> requiredProperties) {
        this(requiredProperties, Collections.emptySet());
    }

    public SimpleServicePropertyValidator(final Set<String> requiredProperties, final Set<String> optionalProperties) {
        this.requiredProperties = requiredProperties;
        this.optionalProperties = optionalProperties;
    }

    @Override
    public void validateAdditionalPropertiesOnCreate(final RequestContext context,
                                                     final Map<String, String> additionalProperties) {
        final Set<String> definedProperties = additionalProperties.keySet();

        final Set<Service> services = context.getServices();
        if (services.size() > 1) {
            throw SingleMessageException.illegalArgument("cannot.validate.multi.service.properties");
        }

        final Service service = services.iterator().next();
        final String serviceKey = service.getKey();

        checkInvalidProperties(definedProperties, serviceKey);
        final Set<String> missedProperties = Sets.difference(requiredProperties, definedProperties);
        if (!missedProperties.isEmpty()) {
            throw SingleMessageException.illegalArgument("service.request.should.contain.additional.properties", serviceKey, StringUtils.join(missedProperties, ", "));
        }
        final Set<String> emptyProperties = additionalProperties.entrySet().stream()
                .filter(e -> trimToEmpty(e.getValue()).isEmpty())
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());
        if (!emptyProperties.isEmpty()) {
            throw SingleMessageException.illegalArgument("service.request.should.contain.not.empty.additional.properties",
                    serviceKey, StringUtils.join(emptyProperties, ", "));
        }
    }

    @Override
    public void validateAdditionalPropertiesOnUpdate(final RequestContext context, final Map<String, String> additionalProperties,
                                                     final Map<String, String> currentAdditionalProperties) {
        final Set<String> definedProperties = additionalProperties.keySet();

        final Set<Service> services = context.getServices();
        if (services.size() > 1) {
            throw SingleMessageException.illegalArgument("cannot.validate.multi.service.properties");
        }

        final Service service = services.iterator().next();
        final String serviceKey = service.getKey();

        checkInvalidProperties(definedProperties, serviceKey);
        Set<String> missedProperties = Sets.difference(requiredProperties, definedProperties);
        if (!missedProperties.isEmpty() && context.getCampaign() != null && context.getCampaign().isAllowedModificationOnMissingAdditionalFields()) {
            // If flag is set then allow to skip required properties which are either not initialized yet or are empty
            final Set<String> currentDefinedProperties = currentAdditionalProperties.keySet();
            final Set<String> currentMissedProperties = Sets.difference(requiredProperties, currentDefinedProperties);
            if (!currentMissedProperties.isEmpty()) {
                missedProperties = Sets.difference(missedProperties, currentMissedProperties);
            }
            final Set<String> currentEmptyProperties = currentAdditionalProperties.entrySet().stream()
                    .filter(e -> trimToEmpty(e.getValue()).isEmpty())
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toSet());
            if (!currentEmptyProperties.isEmpty()) {
                missedProperties = Sets.difference(missedProperties, currentEmptyProperties);
            }
        }
        if (!missedProperties.isEmpty()) {
            throw SingleMessageException.illegalArgument("service.request.should.contain.additional.properties", serviceKey, StringUtils.join(missedProperties, ", "));
        }
        Set<String> emptyProperties = additionalProperties.entrySet().stream()
                .filter(e -> trimToEmpty(e.getValue()).isEmpty())
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());
        if (!emptyProperties.isEmpty() && context.getCampaign() != null && context.getCampaign().isAllowedModificationOnMissingAdditionalFields()) {
            // If flag is set then allow empty values for properties which are already empty and for properties not yet initialized
            final Set<String> currentEmptyProperties = currentAdditionalProperties.entrySet().stream()
                    .filter(e -> trimToEmpty(e.getValue()).isEmpty())
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toSet());
            if (!currentEmptyProperties.isEmpty()) {
                emptyProperties = Sets.difference(emptyProperties, currentEmptyProperties);
            }
            final Set<String> currentDefinedProperties = currentAdditionalProperties.keySet();
            final Set<String> currentMissedProperties = Sets.difference(
                    Sets.union(requiredProperties, optionalProperties), currentDefinedProperties);
            if (!currentMissedProperties.isEmpty()) {
                emptyProperties = Sets.difference(emptyProperties, currentMissedProperties);
            }
        }
        if (!emptyProperties.isEmpty()) {
            throw SingleMessageException.illegalArgument("service.request.should.contain.not.empty.additional.properties",
                    serviceKey, StringUtils.join(emptyProperties, ", "));
        }
    }

    private void checkInvalidProperties(final Set<String> definedProperties, final String serviceKey) {
        final Set<String> invalidProperties = Sets.difference(definedProperties, Sets.union(requiredProperties, optionalProperties));
        if (!invalidProperties.isEmpty()) {
            throw SingleMessageException.illegalArgument("service.request.cannot.contain.properties", serviceKey, StringUtils.join(invalidProperties, ", "));
        }
    }

}
