package ru.yandex.direct.api.v5.entity.adgroups.converter;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.xml.bind.JAXBElement;

import com.google.common.collect.ImmutableMap;
import com.yandex.direct.api.v5.adgroups.AdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.AdGroupGetItem;
import com.yandex.direct.api.v5.adgroups.AdGroupSubtypeEnum;
import com.yandex.direct.api.v5.adgroups.AppAvailabilityStatusEnum;
import com.yandex.direct.api.v5.adgroups.ContentPromotionAdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.ContentPromotionAdGroupGet;
import com.yandex.direct.api.v5.adgroups.DynamicTextAdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.DynamicTextAdGroupGet;
import com.yandex.direct.api.v5.adgroups.DynamicTextFeedAdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.DynamicTextFeedAdGroupGet;
import com.yandex.direct.api.v5.adgroups.MobileAppAdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.MobileAppAdGroupGet;
import com.yandex.direct.api.v5.adgroups.ObjectFactory;
import com.yandex.direct.api.v5.adgroups.PromotedContentTypeGetEnum;
import com.yandex.direct.api.v5.adgroups.SmartAdGroupFieldEnum;
import com.yandex.direct.api.v5.adgroups.SmartAdGroupGet;
import com.yandex.direct.api.v5.adgroups.SourceProcessingStatusEnum;
import com.yandex.direct.api.v5.adgroups.SourceTypeGetEnum;
import com.yandex.direct.api.v5.adgroups.TargetCarrierEnum;
import com.yandex.direct.api.v5.adgroups.TargetDeviceTypeEnum;
import com.yandex.direct.api.v5.adgroups.TextAdGroupFeedParamsFieldEnum;
import com.yandex.direct.api.v5.adgroups.TextAdGroupFeedParamsGet;
import com.yandex.direct.api.v5.general.AdGroupTypesEnum;
import com.yandex.direct.api.v5.general.ArrayOfLong;
import com.yandex.direct.api.v5.general.ArrayOfString;
import com.yandex.direct.api.v5.general.ExtensionModeration;
import com.yandex.direct.api.v5.general.MobileOperatingSystemTypeEnum;
import com.yandex.direct.api.v5.general.ServingStatusEnum;
import com.yandex.direct.api.v5.general.StatusEnum;
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.EnumPropertyFilter;
import ru.yandex.direct.api.v5.entity.adgroups.delegate.AdGroupAnyFieldEnum;
import ru.yandex.direct.api.v5.entity.ads.converter.LogoConverter;
import ru.yandex.direct.common.TranslationService;
import ru.yandex.direct.common.util.PropertyFilter;
import ru.yandex.direct.core.entity.adgroup.model.AdGroup;
import ru.yandex.direct.core.entity.adgroup.model.AdGroupType;
import ru.yandex.direct.core.entity.adgroup.model.ContentPromotionAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.ContentPromotionAdgroupType;
import ru.yandex.direct.core.entity.adgroup.model.CpmBannerAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.CriterionType;
import ru.yandex.direct.core.entity.adgroup.model.DynamicFeedAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.DynamicTextAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.MobileContentAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.MobileContentAdGroupDeviceTypeTargeting;
import ru.yandex.direct.core.entity.adgroup.model.MobileContentAdGroupNetworkTargeting;
import ru.yandex.direct.core.entity.adgroup.model.PerformanceAdGroup;
import ru.yandex.direct.core.entity.adgroup.model.StatusBLGenerated;
import ru.yandex.direct.core.entity.adgroup.model.StatusModerate;
import ru.yandex.direct.core.entity.adgroup.model.StatusPostModerate;
import ru.yandex.direct.core.entity.adgroup.model.TextAdGroup;
import ru.yandex.direct.core.entity.banner.model.PerformanceBannerMain;
import ru.yandex.direct.core.entity.mobilecontent.model.MobileContent;
import ru.yandex.direct.core.entity.mobilecontent.model.OsType;
import ru.yandex.direct.core.entity.mobilecontent.model.StatusIconModerate;

import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.IterableUtils.find;
import static ru.yandex.direct.api.v5.common.RelevanceMatchCategoriesConverter.autotargetingCategoriesFromCore;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;

@ParametersAreNonnullByDefault
@Service("adGroupGetResponseConverter")
public class GetResponseConverter {
    private static final Logger logger = LoggerFactory.getLogger(GetResponseConverter.class);

    private static final Map<AdGroupType, AdGroupTypesEnum> INTERNAL_TO_EXTERNAL_ADGROUP_TYPE =
            ImmutableMap.<AdGroupType, AdGroupTypesEnum>builder()
                    .put(AdGroupType.BASE, AdGroupTypesEnum.TEXT_AD_GROUP)
                    .put(AdGroupType.MOBILE_CONTENT, AdGroupTypesEnum.MOBILE_APP_AD_GROUP)
                    .put(AdGroupType.DYNAMIC, AdGroupTypesEnum.DYNAMIC_TEXT_AD_GROUP)
                    .put(AdGroupType.CPM_BANNER, AdGroupTypesEnum.CPM_BANNER_AD_GROUP)
                    .put(AdGroupType.CPM_VIDEO, AdGroupTypesEnum.CPM_VIDEO_AD_GROUP)
                    .put(AdGroupType.PERFORMANCE, AdGroupTypesEnum.SMART_AD_GROUP)
                    .put(AdGroupType.CONTENT_PROMOTION, AdGroupTypesEnum.CONTENT_PROMOTION_AD_GROUP)
                    .build();

    private static final Map<MobileContentAdGroupDeviceTypeTargeting, TargetDeviceTypeEnum>
            INTERNAL_TO_EXTERNAL_ADGROUP_TARGET_DEVICE_TYPE =
            ImmutableMap.<MobileContentAdGroupDeviceTypeTargeting, TargetDeviceTypeEnum>builder()
                    .put(MobileContentAdGroupDeviceTypeTargeting.PHONE, TargetDeviceTypeEnum.DEVICE_TYPE_MOBILE)
                    .put(MobileContentAdGroupDeviceTypeTargeting.TABLET, TargetDeviceTypeEnum.DEVICE_TYPE_TABLET)
                    .build();

    private static final Map<OsType, MobileOperatingSystemTypeEnum>
            INTERNAL_TO_EXTERNAL_ADGROUP_MOBILE_OPERATING_SYSTEM_TYPE =
            ImmutableMap.<OsType, MobileOperatingSystemTypeEnum>builder()
                    .put(OsType.ANDROID, MobileOperatingSystemTypeEnum.ANDROID)
                    .put(OsType.IOS, MobileOperatingSystemTypeEnum.IOS)
                    .build();

    private static final Map<StatusIconModerate, StatusEnum> INTERNAL_TO_EXTERNAL_ADGROUP_APP_ICON_MODERATION_STATUS =
            ImmutableMap.<StatusIconModerate, StatusEnum>builder()
                    .put(StatusIconModerate.SENDING, StatusEnum.MODERATION)
                    .put(StatusIconModerate.SENT, StatusEnum.MODERATION)
                    .put(StatusIconModerate.READY, StatusEnum.MODERATION)
                    .put(StatusIconModerate.NO, StatusEnum.REJECTED)
                    .put(StatusIconModerate.YES, StatusEnum.ACCEPTED)
                    .build();

    private static final Map<StatusBLGenerated, SourceProcessingStatusEnum>
            INTERNAL_TO_EXTERNAL_ADGROUP_SOURCE_PROCESSING_STATUS =
            ImmutableMap.<StatusBLGenerated, SourceProcessingStatusEnum>builder()
                    .put(StatusBLGenerated.PROCESSING, SourceProcessingStatusEnum.UNPROCESSED)
                    .put(StatusBLGenerated.YES, SourceProcessingStatusEnum.PROCESSED)
                    .put(StatusBLGenerated.NO, SourceProcessingStatusEnum.EMPTY_RESULT)
                    .build();

    private static final Map<ContentPromotionAdgroupType, PromotedContentTypeGetEnum> EXT_PROMOTED_TYPE_BY_INTERNAL =
            ImmutableMap.<ContentPromotionAdgroupType, PromotedContentTypeGetEnum>builder()
                    .put(ContentPromotionAdgroupType.VIDEO, PromotedContentTypeGetEnum.VIDEO)
                    .put(ContentPromotionAdgroupType.COLLECTION, PromotedContentTypeGetEnum.COLLECTION)
                    .put(ContentPromotionAdgroupType.SERVICE, PromotedContentTypeGetEnum.SERVICE)
                    .put(ContentPromotionAdgroupType.EDA, PromotedContentTypeGetEnum.EDA)
                    .build();

    private static final ObjectFactory FACTORY = new ObjectFactory();

    private final EnumPropertyFilter<AdGroupFieldEnum> baseAdGroupPropertyFilter;
    private final EnumPropertyFilter<MobileAppAdGroupFieldEnum> mobileAppAdGroupPropertyFilter;
    private final EnumPropertyFilter<DynamicTextAdGroupFieldEnum> dynamicTextAdGroupPropertyFilter;
    private final EnumPropertyFilter<DynamicTextFeedAdGroupFieldEnum> dynamicTextFeedAdGroupPropertyFilter;
    private final EnumPropertyFilter<SmartAdGroupFieldEnum> smartAdGroupPropertyFilter;
    private final EnumPropertyFilter<ContentPromotionAdGroupFieldEnum> contentPromotionAdGroupPropertyFilter;
    private final EnumPropertyFilter<TextAdGroupFeedParamsFieldEnum> textAdGroupFeedParamsPropertyFilter;

    private final TranslationService translationService;
    private final LogoConverter logoConverter;

    @Autowired
    public GetResponseConverter(PropertyFilter propertyFilter,
                                TranslationService translationService,
                                LogoConverter logoConverter) {
        baseAdGroupPropertyFilter = EnumPropertyFilter.from(AdGroupFieldEnum.class, propertyFilter);
        mobileAppAdGroupPropertyFilter = EnumPropertyFilter.from(MobileAppAdGroupFieldEnum.class, propertyFilter);
        dynamicTextAdGroupPropertyFilter =
                EnumPropertyFilter.from(DynamicTextAdGroupFieldEnum.class, propertyFilter);
        dynamicTextFeedAdGroupPropertyFilter =
                EnumPropertyFilter.from(DynamicTextFeedAdGroupFieldEnum.class, propertyFilter);
        smartAdGroupPropertyFilter =
                EnumPropertyFilter.from(SmartAdGroupFieldEnum.class, propertyFilter);
        contentPromotionAdGroupPropertyFilter =
                EnumPropertyFilter.from(ContentPromotionAdGroupFieldEnum.class, propertyFilter);
        textAdGroupFeedParamsPropertyFilter = EnumPropertyFilter.from(TextAdGroupFeedParamsFieldEnum.class,
                propertyFilter);

        this.translationService = translationService;
        this.logoConverter = logoConverter;
    }

    static AdGroupTypesEnum convertTypeToExternal(AdGroupType internalType) {
        AdGroupTypesEnum externalType = INTERNAL_TO_EXTERNAL_ADGROUP_TYPE.get(internalType);

        if (externalType == null) {
            throw new IllegalArgumentException("not supported adgroup type: " + internalType.toString());
        }

        return externalType;
    }

    static AdGroupSubtypeEnum guessSubtype(AdGroup adGroup) {
        AdGroupSubtypeEnum type;
        if (adGroup instanceof DynamicTextAdGroup) {
            type = AdGroupSubtypeEnum.WEBPAGE;
        } else if (adGroup instanceof DynamicFeedAdGroup) {
            type = AdGroupSubtypeEnum.FEED;
        } else if (adGroup instanceof CpmBannerAdGroup) {
            if (((CpmBannerAdGroup) adGroup).getCriterionType() == CriterionType.KEYWORD) {
                type = AdGroupSubtypeEnum.KEYWORDS;
            } else {
                type = AdGroupSubtypeEnum.USER_PROFILE;
            }
        } else {
            type = AdGroupSubtypeEnum.NONE;
        }
        return type;
    }

    static JAXBElement<ArrayOfString> convertMinusKeywordsToExternal(@Nullable List<String> minusKeywords) {
        return FACTORY.createAdGroupBaseNegativeKeywords(
                (isEmpty(minusKeywords) ? null : new ArrayOfString().withItems(minusKeywords)));
    }

    static JAXBElement<ArrayOfLong> convertLibraryMinusKeywordsToExternal(
            @Nullable List<Long> libraryMinusKeywordsIds) {
        return FACTORY.createAdGroupBaseNegativeKeywordSharedSetIds(isEmpty(libraryMinusKeywordsIds) ? null
                : new ArrayOfLong().withItems(libraryMinusKeywordsIds));
    }

    static JAXBElement<ArrayOfLong> convertRestrictedRegionIdsToExternal(@Nullable List<Long> restrictedRegionIds) {
        return FACTORY.createAdGroupGetItemRestrictedRegionIds(
                isEmpty(restrictedRegionIds) ? null : new ArrayOfLong().withItems(restrictedRegionIds));
    }

    static StatusEnum guessExternalStatus(StatusModerate statusModerate, StatusPostModerate statusPostModerate) {
        StatusEnum externalStatus = StatusEnum.UNKNOWN;

        // NB - order of checks does matter
        if (statusModerate == StatusModerate.NO) {
            externalStatus = StatusEnum.REJECTED;
        } else if (statusModerate == StatusModerate.NEW) {
            externalStatus = StatusEnum.DRAFT;
        } else if ((statusModerate == StatusModerate.SENT
                || statusModerate == StatusModerate.SENDING
                || statusModerate == StatusModerate.READY)
                && (statusPostModerate == StatusPostModerate.NEW
                || statusPostModerate == StatusPostModerate.NO
                || statusPostModerate == StatusPostModerate.READY
                || statusPostModerate == StatusPostModerate.SENT
                || statusPostModerate == StatusPostModerate.REJECTED)) {
            externalStatus = StatusEnum.MODERATION;
        } else if ((statusModerate == StatusModerate.SENT
                || statusModerate == StatusModerate.SENDING
                || statusModerate == StatusModerate.READY)
                && statusPostModerate == StatusPostModerate.YES) {
            externalStatus = StatusEnum.PREACCEPTED;
        } else if (statusModerate == StatusModerate.YES && statusPostModerate == StatusPostModerate.YES) {
            externalStatus = StatusEnum.ACCEPTED;
        }

        if (externalStatus == StatusEnum.UNKNOWN) {
            throw new IllegalArgumentException("Can not guess status for adgroup with statusModerate = "
                    + statusModerate + " and statusPostModerate = " + statusPostModerate);
        }

        return externalStatus;
    }

    static ServingStatusEnum guessExternalServingStatus(Boolean bsRarelyLoaded) {
        return bsRarelyLoaded ? ServingStatusEnum.RARELY_SERVED : ServingStatusEnum.ELIGIBLE;
    }

    static Set<TargetDeviceTypeEnum> convertTargetDeviceTypeToExternal(
            Set<MobileContentAdGroupDeviceTypeTargeting> deviceTypeTargeting) {
        Set<TargetDeviceTypeEnum> result = EnumSet.noneOf(TargetDeviceTypeEnum.class);

        for (MobileContentAdGroupDeviceTypeTargeting internalValue : deviceTypeTargeting) {
            TargetDeviceTypeEnum externalValue = INTERNAL_TO_EXTERNAL_ADGROUP_TARGET_DEVICE_TYPE.get(internalValue);

            if (externalValue == null) {
                throw new IllegalArgumentException("Not supported device type targeting value: " + internalValue);
            }

            result.add(externalValue);
        }

        return result;
    }

    static TargetCarrierEnum convertTargetCarrierToExternal(
            Set<MobileContentAdGroupNetworkTargeting> networkTargeting) {
        return networkTargeting.contains(MobileContentAdGroupNetworkTargeting.CELLULAR)
                ? TargetCarrierEnum.WI_FI_AND_CELLULAR : TargetCarrierEnum.WI_FI_ONLY;
    }

    static MobileOperatingSystemTypeEnum convertAppOperatingSystemTypeToExternal(
            @Nullable MobileContent mobileContent) {
        MobileOperatingSystemTypeEnum externalValue = MobileOperatingSystemTypeEnum.OS_TYPE_UNKNOWN;

        if (mobileContent != null) {
            externalValue = INTERNAL_TO_EXTERNAL_ADGROUP_MOBILE_OPERATING_SYSTEM_TYPE.get(mobileContent.getOsType());

            if (externalValue == null) {
                externalValue = MobileOperatingSystemTypeEnum.OS_TYPE_UNKNOWN;
            }
        }

        return externalValue;
    }

    static StatusEnum guessAppIconModerationStatus(StatusIconModerate internalStatus) {
        StatusEnum externalStatus = INTERNAL_TO_EXTERNAL_ADGROUP_APP_ICON_MODERATION_STATUS.get(internalStatus);

        if (externalStatus == null) {
            throw new IllegalArgumentException(
                    "Not supported mobile content icon moderation status value: " + internalStatus);
        }

        return externalStatus;
    }

    static JAXBElement<ExtensionModeration> covertAppIconModerationToExternal(MobileContent mobileContent) {
        ExtensionModeration result = null;

        if (mobileContent.getIconHash() != null) {
            StatusEnum moderationStatus = guessAppIconModerationStatus(mobileContent.getStatusIconModerate());

            result = new ExtensionModeration().withStatus(moderationStatus).withStatusClarification("");
        }

        return FACTORY.createMobileAppAdGroupGetAppIconModeration(result);
    }

    static AppAvailabilityStatusEnum convertAppAvailabilityStatusToExternal(MobileContent mobileContent) {
        AppAvailabilityStatusEnum result;

        if (mobileContent.getModifyTime() == null) {
            result = AppAvailabilityStatusEnum.UNPROCESSED;
        } else {
            result = mobileContent.getIsAvailable()
                    ? AppAvailabilityStatusEnum.AVAILABLE
                    : AppAvailabilityStatusEnum.NOT_AVAILABLE;
        }

        return result;
    }

    static SourceProcessingStatusEnum convertSourceProcessingStatusToExternal(StatusBLGenerated internalStatus) {
        SourceProcessingStatusEnum externalStatus =
                INTERNAL_TO_EXTERNAL_ADGROUP_SOURCE_PROCESSING_STATUS.get(internalStatus);

        if (externalStatus == null) {
            throw new IllegalArgumentException("Not supported source processing status value: " + internalStatus);
        }

        return externalStatus;
    }

    static SourceTypeGetEnum convertFeedTypeToExternal(@Nullable Long feedId) {
        return feedId != null ? SourceTypeGetEnum.RETAIL_FEED : SourceTypeGetEnum.UNKNOWN;
    }

    static PromotedContentTypeGetEnum convertContentPromotionAdGroupTypeToExternal(
            @Nullable ContentPromotionAdgroupType contentPromotionAdgroupType) {
        if (contentPromotionAdgroupType == null) {
            return PromotedContentTypeGetEnum.UNKNOWN;
        }
        return EXT_PROMOTED_TYPE_BY_INTERNAL
                .getOrDefault(contentPromotionAdgroupType, PromotedContentTypeGetEnum.UNKNOWN);
    }

    public AdGroupGetItem convertToExternalItem(AdGroup adGroup) {

        // NB в perl-версии при невозможности вычисления содержательного значения статуса отдавалось значение
        // UNKNOWN и посылалось письмо, в java-реализации пока просто логируем эту ситуацию

        // TODO как тестировать запись в лог?
        StatusEnum externalStatus;
        try {
            externalStatus = guessExternalStatus(adGroup.getStatusModerate(), adGroup.getStatusPostModerate());
        } catch (IllegalArgumentException e) {
            logger.error("Can not guess status for adgroup #" + adGroup.getId() + " with statusModeration = " + adGroup
                    .getStatusModerate() + " with statusPostModerate = " + adGroup.getStatusPostModerate());
            externalStatus = StatusEnum.UNKNOWN;
        }

        AdGroupGetItem adGroupGetItem =
                new AdGroupGetItem()
                        .withId(adGroup.getId())
                        .withCampaignId(adGroup.getCampaignId())
                        .withType(convertTypeToExternal(adGroup.getType()))
                        .withSubtype(guessSubtype(adGroup))
                        .withName(adGroup.getName() == null ? "" : adGroup.getName())
                        .withTrackingParams(adGroup.getTrackingParams() == null ? "" : adGroup.getTrackingParams())
                        .withNegativeKeywords(convertMinusKeywordsToExternal(adGroup.getMinusKeywords()))
                        .withNegativeKeywordSharedSetIds(
                                convertLibraryMinusKeywordsToExternal(adGroup.getLibraryMinusKeywordsIds()))
                        .withRegionIds(adGroup.getGeo())
                        .withRestrictedRegionIds(convertRestrictedRegionIdsToExternal(adGroup.getRestrictedGeo()))
                        .withStatus(externalStatus)
                        .withServingStatus(guessExternalServingStatus(adGroup.getBsRarelyLoaded()));

        if (adGroup instanceof MobileContentAdGroup) {
            MobileContentAdGroup mobileAppAdGroup = (MobileContentAdGroup) adGroup;

            MobileAppAdGroupGet mobileAppAdGroupGet =
                    new MobileAppAdGroupGet()
                            .withStoreUrl(mobileAppAdGroup.getStoreUrl())
                            .withTargetDeviceType(
                                    convertTargetDeviceTypeToExternal(mobileAppAdGroup.getDeviceTypeTargeting()))
                            .withTargetCarrier(convertTargetCarrierToExternal(mobileAppAdGroup.getNetworkTargeting()))
                            .withAppOperatingSystemType(
                                    convertAppOperatingSystemTypeToExternal(mobileAppAdGroup.getMobileContent()))
                            .withTargetOperatingSystemVersion(mobileAppAdGroup.getMinimalOperatingSystemVersion())
                            .withAppIconModeration(
                                    covertAppIconModerationToExternal(mobileAppAdGroup.getMobileContent()))
                            .withAppAvailabilityStatus(
                                    convertAppAvailabilityStatusToExternal(mobileAppAdGroup.getMobileContent()));

            adGroupGetItem.withMobileAppAdGroup(mobileAppAdGroupGet);
        } else if (adGroup instanceof DynamicTextAdGroup) {
            DynamicTextAdGroup dynamicTextAdGroup = (DynamicTextAdGroup) adGroup;

            DynamicTextAdGroupGet dynamicTextAdGroupGet =
                    new DynamicTextAdGroupGet()
                            .withAutotargetingCategories(autotargetingCategoriesFromCore(
                                    dynamicTextAdGroup.getRelevanceMatchCategories()))
                            .withDomainUrl(
                                    dynamicTextAdGroup.getDomainUrl() == null ? "" : dynamicTextAdGroup.getDomainUrl())
                            .withDomainUrlProcessingStatus(convertSourceProcessingStatusToExternal(
                                    dynamicTextAdGroup.getStatusBLGenerated()));

            adGroupGetItem.withDynamicTextAdGroup(dynamicTextAdGroupGet);
        } else if (adGroup instanceof DynamicFeedAdGroup) {
            DynamicFeedAdGroup dynamicFeedAdGroup = (DynamicFeedAdGroup) adGroup;

            DynamicTextFeedAdGroupGet dynamicTextFeedAdGroupGet =
                    new DynamicTextFeedAdGroupGet()
                            .withAutotargetingCategories(autotargetingCategoriesFromCore(
                                    dynamicFeedAdGroup.getRelevanceMatchCategories()))
                            .withSource(dynamicFeedAdGroup.getFeedId() == null ?
                                    "" : dynamicFeedAdGroup.getFeedId().toString())
                            .withFeedId(dynamicFeedAdGroup.getFeedId())
                            .withSourceType(convertFeedTypeToExternal(dynamicFeedAdGroup.getFeedId()))
                            .withSourceProcessingStatus(convertSourceProcessingStatusToExternal(
                                    dynamicFeedAdGroup.getStatusBLGenerated()));

            adGroupGetItem.withDynamicTextFeedAdGroup(dynamicTextFeedAdGroupGet);
        } else if (adGroup instanceof PerformanceAdGroup) {
            PerformanceAdGroup group = (PerformanceAdGroup) adGroup;
            JAXBElement<String> adTitleSource = FACTORY.createSmartAdGroupGetAdTitleSource(group.getFieldToUseAsName());
            JAXBElement<String> adBodySource = FACTORY.createSmartAdGroupGetAdBodySource(group.getFieldToUseAsBody());
            SmartAdGroupGet smartAdGroup = new SmartAdGroupGet()
                    .withFeedId(group.getFeedId())
                    .withAdTitleSource(adTitleSource)
                    .withAdBodySource(adBodySource);

            PerformanceBannerMain mainBanner = (PerformanceBannerMain) find(group.getBanners(),
                    banner -> banner instanceof PerformanceBannerMain);
            if (mainBanner != null) {
                smartAdGroup
                        .withLogoExtensionHash(FACTORY.createSmartAdGroupGetLogoExtensionHash(
                                mainBanner.getLogoImageHash()))
                        .withLogoExtensionModeration(FACTORY.createSmartAdGroupGetLogoExtensionModeration(
                                logoConverter.convertLogoStatusModerate(mainBanner.getLogoStatusModerate(),
                                        translationService)));
            }

            adGroupGetItem.withSmartAdGroup(smartAdGroup);
        } else if (adGroup instanceof ContentPromotionAdGroup) {
            ContentPromotionAdGroup contentPromotionAdGroup = (ContentPromotionAdGroup) adGroup;

            ContentPromotionAdGroupGet contentPromotionAdGroupGet = new ContentPromotionAdGroupGet()
                    .withPromotedContentType(convertContentPromotionAdGroupTypeToExternal(
                            contentPromotionAdGroup.getContentPromotionType()));

            adGroupGetItem.withContentPromotionAdGroup(contentPromotionAdGroupGet);
        } else if (adGroup instanceof TextAdGroup) {
            TextAdGroup group = (TextAdGroup) adGroup;

            TextAdGroupFeedParamsGet textAdGroupFeedParams = new TextAdGroupFeedParamsGet();
            if (group.getFilteredFeedId() != null) {
                JAXBElement<Long> feedId = FACTORY.createTextAdGroupFeedParamsGetFeedId(group.getOldFeedId());
                JAXBElement<ArrayOfLong> feedFilterCategories =
                        FACTORY.createTextAdGroupFeedParamsGetFeedCategoryIds(new ArrayOfLong().withItems(group.getFeedFilterCategories()));
                textAdGroupFeedParams
                        .withFeedId(feedId)
                        .withFeedCategoryIds(feedFilterCategories);
            }
            adGroupGetItem.withTextAdGroupFeedParams(textAdGroupFeedParams);
        }

        return adGroupGetItem;
    }

    public void filterProperties(List<AdGroupGetItem> getItems, Set<AdGroupAnyFieldEnum> requestedFields) {
        List<String> propertyNames = new ArrayList<>();

        Map<? extends Class<?>, List<AdGroupAnyFieldEnum>> requestedFieldsByType =
                requestedFields.stream().collect(Collectors.groupingBy(AdGroupAnyFieldEnum::getEnumClass));

        List<MobileAppAdGroupGet> mobileAppItems =
                getItems.stream().map(AdGroupGetItem::getMobileAppAdGroup).filter(Objects::nonNull).collect(toList());
        if (!mobileAppItems.isEmpty()) {
            EnumSet<MobileAppAdGroupFieldEnum> mobileAppAdGroupFields = EnumSet.noneOf(MobileAppAdGroupFieldEnum.class);

            if (requestedFieldsByType.containsKey(MobileAppAdGroupFieldEnum.class)) {
                mobileAppAdGroupFields = requestedFieldsByType.get(MobileAppAdGroupFieldEnum.class).stream()
                        .map(e -> (MobileAppAdGroupFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(MobileAppAdGroupFieldEnum.class)));
                propertyNames.add("mobileAppAdGroup");
            }

            mobileAppAdGroupPropertyFilter.filterProperties(mobileAppItems, mobileAppAdGroupFields);
        }

        List<DynamicTextAdGroupGet> dynamicTextItems =
                getItems.stream().map(AdGroupGetItem::getDynamicTextAdGroup).filter(Objects::nonNull).collect(toList());
        if (!dynamicTextItems.isEmpty()) {
            EnumSet<DynamicTextAdGroupFieldEnum> dynamicTextFields = EnumSet.noneOf(DynamicTextAdGroupFieldEnum.class);

            if (requestedFieldsByType.containsKey(DynamicTextAdGroupFieldEnum.class)) {
                dynamicTextFields = requestedFieldsByType.get(DynamicTextAdGroupFieldEnum.class).stream()
                        .map(e -> (DynamicTextAdGroupFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(DynamicTextAdGroupFieldEnum.class)));
                propertyNames.add("dynamicTextAdGroup");
            }

            dynamicTextAdGroupPropertyFilter.filterProperties(dynamicTextItems, dynamicTextFields);
        }

        List<DynamicTextFeedAdGroupGet> dynamicFeedItems =
                getItems.stream().map(AdGroupGetItem::getDynamicTextFeedAdGroup).filter(Objects::nonNull)
                        .collect(toList());
        if (!dynamicFeedItems.isEmpty()) {
            EnumSet<DynamicTextFeedAdGroupFieldEnum> dynamicFeedFields =
                    EnumSet.noneOf(DynamicTextFeedAdGroupFieldEnum.class);

            if (requestedFieldsByType.containsKey(DynamicTextFeedAdGroupFieldEnum.class)) {
                dynamicFeedFields = requestedFieldsByType.get(DynamicTextFeedAdGroupFieldEnum.class).stream()
                        .map(e -> (DynamicTextFeedAdGroupFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(DynamicTextFeedAdGroupFieldEnum.class)));
                propertyNames.add("dynamicTextFeedAdGroup");
            }

            dynamicTextFeedAdGroupPropertyFilter.filterProperties(dynamicFeedItems, dynamicFeedFields);
        }

        List<SmartAdGroupGet> smartItems =
                getItems.stream().map(AdGroupGetItem::getSmartAdGroup).filter(Objects::nonNull).collect(toList());
        if (!smartItems.isEmpty()) {
            EnumSet<SmartAdGroupFieldEnum> smartFields = EnumSet.noneOf(SmartAdGroupFieldEnum.class);

            if (requestedFieldsByType.containsKey(SmartAdGroupFieldEnum.class)) {
                smartFields = requestedFieldsByType.get(SmartAdGroupFieldEnum.class).stream()
                        .map(e -> (SmartAdGroupFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(SmartAdGroupFieldEnum.class)));
                propertyNames.add("smartAdGroup");
            }

            smartAdGroupPropertyFilter.filterProperties(smartItems, smartFields);
        }

        List<ContentPromotionAdGroupGet> contentPromotionItems =
                getItems.stream().map(AdGroupGetItem::getContentPromotionAdGroup).filter(Objects::nonNull)
                        .collect(toList());
        if (!contentPromotionItems.isEmpty()) {
            EnumSet<ContentPromotionAdGroupFieldEnum> contentPromotionFields =
                    EnumSet.noneOf(ContentPromotionAdGroupFieldEnum.class);

            if (requestedFieldsByType.containsKey(ContentPromotionAdGroupFieldEnum.class)) {
                contentPromotionFields = requestedFieldsByType.get(ContentPromotionAdGroupFieldEnum.class).stream()
                        .map(e -> (ContentPromotionAdGroupFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(ContentPromotionAdGroupFieldEnum.class)));
                propertyNames.add("contentPromotionAdGroup");
            }

            contentPromotionAdGroupPropertyFilter.filterProperties(contentPromotionItems, contentPromotionFields);
        }

        List<TextAdGroupFeedParamsGet> textAdGroupFeedParamItems =
                getItems.stream().map(AdGroupGetItem::getTextAdGroupFeedParams).filter(Objects::nonNull)
                        .collect(toList());
        if (!textAdGroupFeedParamItems.isEmpty()) {
            EnumSet<TextAdGroupFeedParamsFieldEnum> textAdGroupFeedParamsFields =
                    EnumSet.noneOf(TextAdGroupFeedParamsFieldEnum.class);

            if (requestedFieldsByType.containsKey(TextAdGroupFeedParamsFieldEnum.class)) {
                textAdGroupFeedParamsFields = requestedFieldsByType.get(TextAdGroupFeedParamsFieldEnum.class).stream()
                        .map(e -> (TextAdGroupFeedParamsFieldEnum) e.getValue())
                        .collect(toCollection(() -> EnumSet.noneOf(TextAdGroupFeedParamsFieldEnum.class)));
                propertyNames.add("textAdGroupFeedParams");
            }

            textAdGroupFeedParamsPropertyFilter.filterProperties(textAdGroupFeedParamItems,
                    textAdGroupFeedParamsFields);
        }

        EnumSet<AdGroupFieldEnum> baseAdGroupFields = EnumSet.noneOf(AdGroupFieldEnum.class);
        if (requestedFieldsByType.containsKey(AdGroupFieldEnum.class)) {
            baseAdGroupFields = requestedFieldsByType.get(AdGroupFieldEnum.class).stream()
                    .map(e -> (AdGroupFieldEnum) e.getValue())
                    .collect(toCollection(() -> EnumSet.noneOf(AdGroupFieldEnum.class)));
        }
        propertyNames.addAll(mapList(baseAdGroupFields, baseAdGroupPropertyFilter.getEnumToFieldMap()::get));
        baseAdGroupPropertyFilter.filterPropertiesByNames(getItems, propertyNames);
    }
}
