package ru.yandex.direct.api.v5.entity.audiencetargets.delegate;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.annotation.Nonnull;

import com.yandex.direct.api.v5.audiencetargets.AudienceTargetSetBidsItem;
import com.yandex.direct.api.v5.audiencetargets.SetBidsRequest;
import com.yandex.direct.api.v5.audiencetargets.SetBidsResponse;
import one.util.streamex.StreamEx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.direct.api.v5.common.ApiPathConverter;
import ru.yandex.direct.api.v5.converter.ResultConverter;
import ru.yandex.direct.api.v5.entity.OperationOnListDelegate;
import ru.yandex.direct.api.v5.entity.audiencetargets.converter.AudienceTargetsHelperConverter;
import ru.yandex.direct.api.v5.entity.audiencetargets.validation.AudienceTargetsDefectPresentations;
import ru.yandex.direct.api.v5.result.ApiMassResult;
import ru.yandex.direct.api.v5.result.ApiResult;
import ru.yandex.direct.api.v5.security.ApiAuthenticationSource;
import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.common.db.PpcPropertiesSupport;
import ru.yandex.direct.core.entity.adgroup.model.AdGroupType;
import ru.yandex.direct.core.entity.adgroup.service.AdGroupService;
import ru.yandex.direct.core.entity.bids.container.SetBidItem;
import ru.yandex.direct.core.entity.feature.service.FeatureService;
import ru.yandex.direct.core.entity.retargeting.container.RetargetingSelection;
import ru.yandex.direct.core.entity.retargeting.model.Retargeting;
import ru.yandex.direct.core.entity.retargeting.model.TargetInterest;
import ru.yandex.direct.core.entity.retargeting.service.RetargetingService;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.i18n.types.Identity;
import ru.yandex.direct.multitype.entity.LimitOffset;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.ListValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.ValidationResult;

import static java.util.Collections.emptyList;
import static ru.yandex.direct.api.v5.entity.audiencetargets.Constants.MAX_BID_ADGROUPIDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.audiencetargets.Constants.MAX_BID_CAMPAIGNIDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.audiencetargets.Constants.MAX_BID_IDS_PER_REQUEST;
import static ru.yandex.direct.api.v5.entity.audiencetargets.validation.AudienceTargetsAdGroupTypeConstraints.allowedAdGroupTypeForSetBids;
import static ru.yandex.direct.api.v5.validation.DefectTypes.maxElementsPerRequest;
import static ru.yandex.direct.api.v5.validation.DefectTypes.translations;
import static ru.yandex.direct.api.v5.validation.SetBidsConstraints.bidsListSizeMaxLimit;
import static ru.yandex.direct.api.v5.validation.constraints.Constraints.eachNotNull;

@Service
public class SetBidsAudienceTargetsDelegate
        extends OperationOnListDelegate<SetBidsRequest, SetBidsResponse, SetBidItem, SetBidItem> {
    private static final Logger logger = LoggerFactory.getLogger(SetBidsAudienceTargetsDelegate.class);

    private final AudienceTargetsHelperConverter apiToDomainConvertService;
    private final ResultConverter resultConverter;
    private final RetargetingService retargetingService;
    private final AdGroupService adGroupService;

    @Autowired
    public SetBidsAudienceTargetsDelegate(AudienceTargetsHelperConverter apiToDomainConvertService,
                                          ApiAuthenticationSource auth,
                                          ResultConverter resultConverter,
                                          RetargetingService retargetingService,
                                          AdGroupService adGroupService,
                                          PpcPropertiesSupport ppcPropertiesSupport,
                                          FeatureService featureService) {
        super(ApiPathConverter.forAudienceTargets(), auth, ppcPropertiesSupport, featureService);
        this.apiToDomainConvertService = apiToDomainConvertService;
        this.resultConverter = resultConverter;
        this.retargetingService = retargetingService;
        this.adGroupService = adGroupService;
    }

    @Override
    public ValidationResult<SetBidsRequest, DefectType> validateRequest(SetBidsRequest request) {
        ItemValidationBuilder<SetBidsRequest, DefectType> vb = ItemValidationBuilder.of(request);
        vb.item(request.getBids(), "Bids")
                .check(eachNotNull())
                .check(bidsListSizeMaxLimit(AudienceTargetSetBidsItem::getId, MAX_BID_IDS_PER_REQUEST,
                        t -> maxElementsPerRequest(t)
                                .withDetailedMessage(translations().maxElementsPerRequestRetargetingIdsDetailed(
                                        new Identity(t))),
                        AudienceTargetSetBidsItem::getAdGroupId, MAX_BID_ADGROUPIDS_PER_REQUEST,
                        t -> maxElementsPerRequest(t)
                                .withDetailedMessage(translations().maxElementsPerRequestRetargetingAdGroupIdsDetailed(
                                        new Identity(t))),
                        AudienceTargetSetBidsItem::getCampaignId, MAX_BID_CAMPAIGNIDS_PER_REQUEST,
                        t -> maxElementsPerRequest(t)
                                .withDetailedMessage(translations().maxElementsPerRequestRetargetingCampaignIdsDetailed(
                                        new Identity(t)))), When.isValid());

        return vb.getResult();
    }

    @Override
    public List<SetBidItem> convertRequest(SetBidsRequest externalRequest) {
        logger.debug("convertRequest({})", externalRequest);
        return apiToDomainConvertService.convertSetBidsRequest(externalRequest);
    }

    @Nonnull
    @Override
    public ValidationResult<List<SetBidItem>, DefectType> validateInternalRequest(List<SetBidItem> internalRequest) {
        logger.debug("validateInternalRequest({})", internalRequest);
        Long operatorUid = auth.getOperator().getUid();
        ClientId clientId = auth.getChiefSubclient().getClientId();

        List<TargetInterest> targetInterests = getRetargetings(internalRequest, clientId, operatorUid);

        Map<Long, TargetInterest> targetInterestById = StreamEx.of(targetInterests)
                .mapToEntry(Retargeting::getId, ti -> ti)
                .distinctKeys()
                .toMap();

        Set<Long> adGroupIds = StreamEx.of(targetInterests)
                .map(Retargeting::getAdGroupId)
                .filter(Objects::nonNull)
                .toSet();

        Map<Long, AdGroupType> adGroupTypes = adGroupService.getAdGroupTypes(clientId, adGroupIds);
        Set<AdGroupType> allowedAdGroupTypes = getAllowedAdGroupTypes();
        return ListValidationBuilder.<SetBidItem, DefectType>of(internalRequest)
                .checkEach(allowedAdGroupTypeForSetBids(targetInterestById, adGroupTypes, allowedAdGroupTypes))
                .getResult();
    }

    @Override
    public ApiMassResult<SetBidItem> processList(List<SetBidItem> validItems) {
        logger.debug("processList({})", validItems);
        Long operatorId = auth.getOperator().getUid();
        ClientId clientId = auth.getChiefSubclient().getClientId();
        Set<AdGroupType> allowedAdGroupTypes = getAllowedAdGroupTypes();
        return resultConverter.toApiMassResult(retargetingService.setBidsApi(validItems, clientId, operatorId,
                allowedAdGroupTypes),
                AudienceTargetsDefectPresentations.HOLDER);
    }

    @Override
    public SetBidsResponse convertResponse(ApiResult<List<ApiResult<SetBidItem>>> result) {
        logger.debug("convertResponse({})", result);
        return apiToDomainConvertService.convertToSetBidsResponse(result, apiPathConverter);
    }

    private List<TargetInterest> getRetargetings(List<SetBidItem> internalRequest, ClientId clientId,
                                                 Long operatorUid) {
        RetargetingSelection retargetingSelection = new RetargetingSelection();
        retargetingSelection.withIds(
                StreamEx.of(internalRequest)
                        .map(SetBidItem::getId)
                        .filter(Objects::nonNull)
                        .toList());
        retargetingSelection.withAdGroupIds(
                StreamEx.of(internalRequest)
                        .map(SetBidItem::getAdGroupId)
                        .filter(Objects::nonNull)
                        .toList());
        retargetingSelection.withCampaignIds(
                StreamEx.of(internalRequest)
                        .map(SetBidItem::getCampaignId)
                        .filter(Objects::nonNull)
                        .toList());

        return retargetingSelection.isEmpty() ? emptyList() :
                retargetingService.getRetargetings(retargetingSelection, clientId, operatorUid,
                        LimitOffset.maxLimited());
    }
}
