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

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import com.google.common.collect.ImmutableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.api.v5.validation.DefectTypes;
import ru.yandex.direct.core.entity.bids.container.SetBidItem;
import ru.yandex.direct.core.entity.campaign.service.accesschecker.CampaignAccessibiltyChecker;
import ru.yandex.direct.core.entity.campaign.service.accesschecker.CampaignSubObjectAccessChecker;
import ru.yandex.direct.core.entity.campaign.service.accesschecker.CampaignSubObjectAccessCheckerFactory;
import ru.yandex.direct.core.validation.CommonDefectTranslations;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.validation.builder.Constraint;
import ru.yandex.direct.validation.builder.ListValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.ValidationResult;

import static ru.yandex.direct.api.v5.entity.bids.validation.BidsDefectTypes.requiredAnyOfSetBidFields;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;
import static ru.yandex.direct.validation.builder.Constraint.fromPredicate;


@Component
public class BidsInternalValidationService {
    private final CampaignSubObjectAccessCheckerFactory campaignSubObjectAccessCheckerFactory;
    private static final List<Function<SetBidItem, Long>> ID_GETTERS = ImmutableList.of(
            SetBidItem::getId, SetBidItem::getAdGroupId, SetBidItem::getCampaignId);

    @Autowired
    public BidsInternalValidationService(CampaignSubObjectAccessCheckerFactory campaignSubObjectAccessCheckerFactory) {
        this.campaignSubObjectAccessCheckerFactory = campaignSubObjectAccessCheckerFactory;
    }

    public ValidationResult<List<SetBidItem>, DefectType> validateInternalRequest(
            List<SetBidItem> setBidItemList,
            Long operatorUid,
            ClientId clientId,
            CampaignAccessibiltyChecker campaignAccessibiltyChecker) {
        ListValidationBuilder<SetBidItem, DefectType> vb = ListValidationBuilder.of(setBidItemList);

        if (setBidItemList == null || setBidItemList.isEmpty()) {
            return vb.getResult();
        }
        vb.checkEach(fromPredicate(item -> ID_GETTERS.stream().anyMatch(getter -> getter.apply(item) != null),
                requiredAnyOfSetBidFields()));
        // Подразумевается, что все элементы одного типа. Это должно проверяться на входящем запросе.
        Function<SetBidItem, Long> idGetter;
        Function<Collection<Long>, CampaignSubObjectAccessChecker> getChecker;
        if (setBidItemList.get(0).getId() != null) {
            idGetter = SetBidItem::getId;
            getChecker = ids -> campaignSubObjectAccessCheckerFactory.newBidChecker(operatorUid, clientId, ids);
        } else if (setBidItemList.get(0).getAdGroupId() != null) {
            idGetter = SetBidItem::getAdGroupId;
            getChecker = ids -> campaignSubObjectAccessCheckerFactory.newAdGroupChecker(operatorUid, clientId, ids);
        } else {
            idGetter = SetBidItem::getCampaignId;
            getChecker = ids -> campaignSubObjectAccessCheckerFactory.newCampaignChecker(operatorUid, clientId, ids);
        }

        CampaignSubObjectAccessChecker checker = getChecker.apply(mapList(setBidItemList, idGetter));

        return vb.checkEach(notInUnsupported(idGetter, checker, campaignAccessibiltyChecker),
                When.isValid()).getResult();
    }

    private Constraint<SetBidItem, DefectType> notInUnsupported(Function<SetBidItem, Long> getIdFromItem,
                                                                CampaignSubObjectAccessChecker checker,
                                                                CampaignAccessibiltyChecker accessibiltyChecker) {
        Set<Long> unsupported = checker.getUnsupported(accessibiltyChecker);
        Predicate<SetBidItem> objectInSupportedCampaign = (item) -> {
            Long id = getIdFromItem.apply(item);
            return id == null || !unsupported.contains(id);
        };
        return fromPredicate(
                objectInSupportedCampaign, DefectTypes.notSupported()
                        .withDetailedMessage(CommonDefectTranslations.INSTANCE.adGroupTypeNotSupportedDetailed()));
    }
}

