package ru.yandex.direct.api.v5.entity.keywordbids.validation;

import java.util.List;
import java.util.function.Function;

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.ImmutableList;
import com.yandex.direct.api.v5.keywordbids.KeywordBidSetItem;
import com.yandex.direct.api.v5.keywordbids.SetRequest;
import org.springframework.stereotype.Service;

import ru.yandex.direct.api.v5.entity.bids.service.validation.SetBidsRequestValidationService;
import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.ValidationResult;

import static ru.yandex.direct.api.v5.entity.bids.Constants.MAX_BID_ADGROUPIDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.bids.Constants.MAX_BID_CAMPAIGNIDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.bids.Constants.MAX_BID_IDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.bids.validation.BidsDefectTypes.maxAdGroupsBidsPerRequest;
import static ru.yandex.direct.api.v5.entity.bids.validation.BidsDefectTypes.maxCampBidsPerRequest;
import static ru.yandex.direct.api.v5.entity.bids.validation.BidsDefectTypes.maxKeywordBidsPerRequest;
import static ru.yandex.direct.api.v5.entity.bids.validation.BidsDefectTypes.requiredAnyOfSetBidFields;
import static ru.yandex.direct.api.v5.validation.DefectTypes.mixedTypes;
import static ru.yandex.direct.api.v5.validation.SetBidsConstraints.bidsListSizeMaxLimit;
import static ru.yandex.direct.api.v5.validation.constraints.Constraints.eachNotNull;
import static ru.yandex.direct.validation.builder.Constraint.fromPredicate;


/**
 * Клон {@link SetBidsRequestValidationService}
 */
@Service
@ParametersAreNonnullByDefault
public class SetKeywordBidsRequestValidationService {
    private static final String BIDS_FIELD = "KeywordBids";
    private static final List<Function<KeywordBidSetItem, Long>> ID_GETTERS = ImmutableList.of(
            KeywordBidSetItem::getKeywordId, KeywordBidSetItem::getAdGroupId, KeywordBidSetItem::getCampaignId);

    public ValidationResult<SetRequest, DefectType> validate(SetRequest externalRequest) {
        ItemValidationBuilder<SetRequest, DefectType> vb = ItemValidationBuilder.of(externalRequest);
        vb.list(externalRequest.getKeywordBids(), BIDS_FIELD)
                .check(eachNotNull())
                .check(bidsListSizeMaxLimit(KeywordBidSetItem::getKeywordId, MAX_BID_IDS_PER_REQUEST,
                        t -> maxKeywordBidsPerRequest(),
                        KeywordBidSetItem::getAdGroupId, MAX_BID_ADGROUPIDS_PER_REQUEST,
                        t -> maxAdGroupsBidsPerRequest(),
                        KeywordBidSetItem::getCampaignId, MAX_BID_CAMPAIGNIDS_PER_REQUEST,
                        t -> maxCampBidsPerRequest()), When.isValid())
                // проверка на то, что хотя бы в одном элементе запроса указан id кампании, адгруппы или кейворда
                .check(fromPredicate(this::allItemsHaveNoSpecfiedId, requiredAnyOfSetBidFields()),
                        When.isValid())
                // проверка нужна для validateRequestInternal, который рассчитывает на то, что все id одного типа
                .check(fromPredicate(this::allItemsHaveIdsOfTheSameType, mixedTypes()), When.isValid());
        return vb.getResult();
    }

    private boolean allItemsHaveIdsOfTheSameType(List<KeywordBidSetItem> items) {
        return ID_GETTERS.stream()
                .filter(getter -> items.stream()
                        .allMatch(t -> itemHasNoSpecifiedId(t) || itemHasSpecifiedId(t, getter)))
                .findFirst().isPresent();
    }

    private boolean itemHasExactNumberOfIds(KeywordBidSetItem item, long number) {
        return ID_GETTERS.stream().filter(getter -> itemHasSpecifiedId(item, getter)).count() == number;
    }

    private boolean itemHasNoSpecifiedId(KeywordBidSetItem item) {
        return itemHasExactNumberOfIds(item, 0L);
    }

    private boolean allItemsHaveNoSpecfiedId(List<KeywordBidSetItem> items) {
        return !items.stream().allMatch(this::itemHasNoSpecifiedId);
    }

    private boolean itemHasSpecifiedId(KeywordBidSetItem item, Function<KeywordBidSetItem, Long> getter) {
        return getter.apply(item) != null;
    }
}
