package ru.yandex.direct.api.v5.entity.bids.service.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.bids.BidSetItem;
import com.yandex.direct.api.v5.bids.SetRequest;
import org.springframework.stereotype.Service;

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;


@Service
@ParametersAreNonnullByDefault
public class SetBidsRequestValidationService {
    private static final String BIDS_FIELD = "Bids";
    private static final List<Function<BidSetItem, Long>> ID_GETTERS = ImmutableList.of(
            BidSetItem::getKeywordId, BidSetItem::getAdGroupId, BidSetItem::getCampaignId);

    public ValidationResult<SetRequest, DefectType> validate(SetRequest externalRequest) {
        ItemValidationBuilder<SetRequest, DefectType> vb = ItemValidationBuilder.of(externalRequest);
        vb.list(externalRequest.getBids(), BIDS_FIELD)
                .check(eachNotNull())
                .check(bidsListSizeMaxLimit(BidSetItem::getKeywordId, MAX_BID_IDS_PER_REQUEST,
                        t -> maxKeywordBidsPerRequest(),
                        BidSetItem::getAdGroupId, MAX_BID_ADGROUPIDS_PER_REQUEST,
                        t -> maxAdGroupsBidsPerRequest(),
                        BidSetItem::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().withDetailedMessage(m -> "")), When.isValid());
        return vb.getResult();
    }

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

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

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

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

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