package ru.yandex.direct.web.entity.uac.service

import java.math.BigDecimal
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import ru.yandex.direct.core.entity.bidmodifiers.service.BidModifierService
import ru.yandex.direct.core.entity.campaign.service.CampaignService
import ru.yandex.direct.core.entity.campaign.service.uc.UcCampaignService
import ru.yandex.direct.core.entity.campaign.service.validation.DisableDomainValidationService
import ru.yandex.direct.core.entity.client.service.ClientGeoService
import ru.yandex.direct.core.entity.client.service.ClientLimitsService
import ru.yandex.direct.core.entity.client.service.ClientService
import ru.yandex.direct.core.entity.feature.service.FeatureService
import ru.yandex.direct.core.entity.feed.service.FeedService
import ru.yandex.direct.core.entity.hypergeo.service.HyperGeoService
import ru.yandex.direct.core.entity.image.repository.BannerImageFormatRepository
import ru.yandex.direct.core.entity.performancefilter.service.PerformanceFilterStorage
import ru.yandex.direct.core.entity.retargeting.service.uc.UcRetargetingConditionService
import ru.yandex.direct.core.entity.retargeting.service.validation2.AddRetargetingConditionValidationService2
import ru.yandex.direct.core.entity.sspplatform.repository.SspPlatformsRepository
import ru.yandex.direct.core.entity.uac.converter.UacGrutAdGroupBriefConverter.toAdGroupBriefGrutModel
import ru.yandex.direct.core.entity.uac.converter.UacGrutCampaignConverter.readAssetLinks
import ru.yandex.direct.core.entity.uac.converter.UacGrutCampaignConverter.toEAdCampaignType
import ru.yandex.direct.core.entity.uac.grut.GrutTransactionProvider
import ru.yandex.direct.core.entity.uac.model.CampaignStatuses
import ru.yandex.direct.core.entity.uac.model.Content
import ru.yandex.direct.core.entity.uac.model.MediaType
import ru.yandex.direct.core.entity.uac.model.Sitelink
import ru.yandex.direct.core.entity.uac.model.Status
import ru.yandex.direct.core.entity.uac.model.TargetStatus
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbUtils
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbUtils.generateRandomIdLong
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbUtils.toIdLong
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbUtils.toIdString
import ru.yandex.direct.core.entity.uac.service.CpmBannerCampaignService
import ru.yandex.direct.core.entity.uac.service.GrutUacCampaignService
import ru.yandex.direct.core.entity.uac.service.GrutUacContentService
import ru.yandex.direct.core.entity.uac.service.RmpCampaignService
import ru.yandex.direct.core.entity.uac.service.UacDisabledDomainsService
import ru.yandex.direct.core.entity.user.model.User
import ru.yandex.direct.core.grut.api.AssetGrut
import ru.yandex.direct.core.grut.api.SitelinkAssetGrut
import ru.yandex.direct.core.grut.api.TextAssetGrut
import ru.yandex.direct.core.grut.api.TitleAssetGrut
import ru.yandex.direct.core.grut.replication.GrutApiService
import ru.yandex.direct.dbutil.sharding.ShardHelper
import ru.yandex.direct.feature.FeatureName
import ru.yandex.direct.grid.processing.service.campaign.uc.UcCampaignMutationService
import ru.yandex.direct.libs.mirrortools.utils.HostingsHandler
import ru.yandex.direct.web.entity.uac.converter.UacCampaignConverter.AssetLinkInfo
import ru.yandex.direct.web.entity.uac.converter.UacCampaignConverter.buildCampaignSpec
import ru.yandex.direct.web.entity.uac.converter.UacCampaignConverter.makeFaviconLink
import ru.yandex.direct.web.entity.uac.converter.UacCampaignConverter.toUacYdbCampaign
import ru.yandex.direct.web.entity.uac.model.UacAdGroupBriefRequest
import ru.yandex.direct.web.entity.uac.model.UacCampaign
import ru.yandex.direct.web.entity.uac.model.UacModifyCampaignDataContainer
import ru.yandex.grut.objects.proto.MediaType.EMediaType.MT_TEXT
import ru.yandex.grut.objects.proto.MediaType.EMediaType.MT_TITLE
import ru.yandex.grut.objects.proto.client.Schema
import ru.yandex.grut.objects.proto.client.Schema.TCampaignMeta

@Service
class GrutUacCampaignAddService(
    uacMobileAppService: UacMobileAppService,
    uacModifyCampaignDataContainerFactory: UacModifyCampaignDataContainerFactory,
    rmpCampaignService: RmpCampaignService,
    cpmBannerCampaignService: CpmBannerCampaignService,
    ucCampaignMutationService: UcCampaignMutationService,
    ucRetargetingConditionService: UcRetargetingConditionService,
    uacGoalsService: UacGoalsService,
    ucCampaignService: UcCampaignService,
    clientGeoService: ClientGeoService,
    uacCampaignValidationService: UacCampaignValidationService,
    bidModifierService: BidModifierService,
    uacPropertiesService: UacPropertiesService,
    clientService: ClientService,
    uacAdjustmentsService: UacAdjustmentsService,
    sspPlatformsRepository: SspPlatformsRepository,
    hostingsHandler: HostingsHandler,
    bannerImageFormatRepository: BannerImageFormatRepository,
    private val shardHelper: ShardHelper,
    cpmBannerService: UacCpmBannerService,
    clientLimitsService: ClientLimitsService,
    disableDomainValidationService: DisableDomainValidationService,
    rmpStrategyValidatorFactory: RmpStrategyValidatorFactory,
    uacDisabledDomainsService: UacDisabledDomainsService,
    retargetingConditionValidationService: AddRetargetingConditionValidationService2,
    private val grutApiService: GrutApiService,
    private val hyperGeoService: HyperGeoService,
    private val grutUacContentService: GrutUacContentService,
    private val grutUacCampaignWebService: GrutUacCampaignWebService,
    private val featureService: FeatureService,
    private val uacRetargetingConditionService: UacRetargetingConditionService,
    grutUacCampaignService: GrutUacCampaignService,
    campaignService: CampaignService,
    grutTransactionProvider: GrutTransactionProvider,
    feedService: FeedService,
    filterSchemaStorage: PerformanceFilterStorage,
) : BaseUacCampaignAddService(
    uacMobileAppService,
    uacModifyCampaignDataContainerFactory,
    rmpCampaignService,
    cpmBannerCampaignService,
    ucCampaignMutationService,
    ucRetargetingConditionService,
    uacGoalsService,
    ucCampaignService,
    clientGeoService,
    uacCampaignValidationService,
    featureService,
    bidModifierService,
    uacPropertiesService,
    clientService,
    sspPlatformsRepository,
    hostingsHandler,
    bannerImageFormatRepository,
    shardHelper,
    cpmBannerService,
    clientLimitsService,
    disableDomainValidationService,
    uacAdjustmentsService,
    rmpStrategyValidatorFactory,
    uacDisabledDomainsService,
    retargetingConditionValidationService,
    grutUacCampaignService,
    campaignService,
    grutTransactionProvider,
    feedService,
    filterSchemaStorage
) {

    companion object {
        private val logger = LoggerFactory.getLogger(BaseUacCampaignAddService::class.java)
    }

    override fun getLogger() = logger

    override fun saveBrief(
        directCampaignId: Long,
        operator: User,
        subjectUser: User,
        createDataContainer: UacModifyCampaignDataContainer,
        contents: Collection<Content>,
        campaignStatuses: CampaignStatuses,
    ): UacCampaign {
        val uacMergeEqualsAssetsEnabled = featureService
            .isEnabledForClientId(subjectUser.clientId, FeatureName.UAC_MERGE_EQUALS_ASSETS_ENABLED)
        val newAssetLinkIdsGenerationEnabled = featureService
            .isEnabledForClientId(subjectUser.clientId, FeatureName.UC_UAC_NEW_ASSET_LINK_IDS_GENERATION_IN_GRUT)
        val uacMultipleAdGroupsEnabled = featureService
            .isEnabledForClientId(subjectUser.clientId, FeatureName.UAC_MULTIPLE_AD_GROUPS_ENABLED)

        // Для null собираем данные для заявки на кампанию, для остальных - данные для групповых заявок
        val uacAdGroupBriefRequests: List<UacAdGroupBriefRequest?> =
            if (createDataContainer.adGroupBriefRequests.isNullOrEmpty()) listOf<UacAdGroupBriefRequest?>(null)
            else listOf(null).plus(createDataContainer.adGroupBriefRequests)

        val adGroupDataToMediaAssets =
            mergeMediaAssets(uacAdGroupBriefRequests, contents, uacMergeEqualsAssetsEnabled)
        val adGroupDataToTextAssets =
            mergeTextAssets(subjectUser, createDataContainer, uacAdGroupBriefRequests, uacMergeEqualsAssetsEnabled)

        val uacYdbCampaign = toUacYdbCampaign(createDataContainer, subjectUser.clientId.toString())
        val grutAssetIdsCreated = mutableSetOf<Long>()

        for ((index, adGroupData) in uacAdGroupBriefRequests.withIndex()) {
            // Первой должна идти кампания, чтобы групповые заявки могли завязаться на нее
            require(index > 0 || adGroupData == null) { "The first brief is not a campaign" }

            val textAssetsGrut = adGroupDataToTextAssets[adGroupData]!!
            val briefContents = adGroupDataToMediaAssets[adGroupData]!!

            val nonMediaAssetIdToOrder = saveNonMediaAssets(textAssetsGrut, grutAssetIdsCreated)
            grutAssetIdsCreated.addAll(nonMediaAssetIdToOrder.keys.map { it.toIdLong() })

            val mediaAssetIdToOrder = briefContents
                .mapIndexed { order, content -> content.id to order }
                .toMap()

            val assetLinkInfos = (nonMediaAssetIdToOrder + mediaAssetIdToOrder)
                .map {
                    AssetLinkInfo(
                        id = if (newAssetLinkIdsGenerationEnabled) UacYdbUtils.generateUniqueRandomId() else it.key,
                        assetId = it.key,
                        order = it.value
                    )
                }

            val campaignSpec = buildCampaignSpec(uacYdbCampaign, assetLinkInfos)
            if (index == 0) {
                grutApiService.briefGrutApi.createBrief(
                    Schema.TCampaign.newBuilder().apply {
                        meta = TCampaignMeta.newBuilder().apply {
                            id = directCampaignId
                            campaignType = createDataContainer.advType.toEAdCampaignType()
                            clientId = subjectUser.clientId.asLong()
                        }.build()
                        spec = campaignSpec
                    }.build()
                )
            }
            // Если фронт не передавал групповых заявок - создаем новую от заявки на кампанию при включенной фиче
            // Либо обрабатываем те что фронт передал
            if (uacMultipleAdGroupsEnabled && ((index == 0 && uacAdGroupBriefRequests.size == 1) || index > 0)) {
                val assetLinks = readAssetLinks(
                    campaignSpec.briefAssetLinks,
                    campaignSpec.briefAssetLinksStatuses,
                    directCampaignId.toIdString()
                )
                val adGroupBriefGrutModel = toAdGroupBriefGrutModel(
                    directCampaignId,
                    uacYdbCampaign,
                    assetLinks = assetLinks
                )
                val adGroupBriefId = grutApiService.adGroupBriefGrutApi
                    .createAdGroupBriefs(listOf(adGroupBriefGrutModel))[0]
                logger.info("Created ad group brief $adGroupBriefId for campaign $directCampaignId")
            }
        }

        val shard = shardHelper.getShardByCampaignId(directCampaignId)
        val campaignSimple = grutUacCampaignWebService.getCampaignSimple(shard, directCampaignId)

        return UacCampaign(
            directCampaignId.toIdString(),
            grutUacCampaignWebService.getCampaignAccess(campaignSimple, directCampaignId, subjectUser, operator),
            grutUacCampaignWebService.getAgencyInfo(campaignSimple),
            grutUacCampaignWebService.getManagerInfo(campaignSimple),
            0,                  // Компания только добавилась, поэтому у неё 0 показов
            BigDecimal.ZERO,            // Денег тоже 0
            createDataContainer.displayName,
            createDataContainer.advType,
            createDataContainer.appInfo,
            contents.toList(),
            createDataContainer.texts,
            createDataContainer.titles,
            createDataContainer.regions,
            grutUacCampaignWebService.regionIdsToNames(operator.clientId, createDataContainer.regions),
            createDataContainer.minusRegions,
            grutUacCampaignWebService.regionIdsToNames(operator.clientId, createDataContainer.minusRegions),
            createDataContainer.trackingUrl?.getUrl(),
            createDataContainer.impressionUrl?.getUrl(),
            createDataContainer.href,
            makeFaviconLink(createDataContainer.advType, createDataContainer.href),
            createDataContainer.targetId,
            createDataContainer.weekLimit,
            createDataContainer.cpa,
            createDataContainer.createdTime,
            createDataContainer.updatedTime,
            createDataContainer.startedTime,
            TargetStatus.STOPPED,
            Status.DRAFT,
            false,
            null,
            directCampaignId,
            null,
            createDataContainer.contentFlags,
            null,
            createDataContainer.limitPeriod,
            createDataContainer.skadNetworkEnabled,
            createDataContainer.adultContentEnabled,
            createDataContainer.hyperGeoId?.let { hyperGeoService.getHyperGeoById(subjectUser.clientId, it) },
            createDataContainer.keywords,
            createDataContainer.minusKeywords,
            createDataContainer.socdem,
            createDataContainer.deviceTypes,
            createDataContainer.inventoryTypes,
            createDataContainer.goals,
            createDataContainer.counters,
            createDataContainer.permalinkId,
            createDataContainer.phoneId,
            createDataContainer.calltrackingSettingsId,
            createDataContainer.sitelinks,
            createDataContainer.timeTarget,
            null,
            createDataContainer.strategy,
            retargetingCondition = uacRetargetingConditionService.fillRetargetingCondition(
                shard, campaignSimple, createDataContainer.retargetingCondition),
            createDataContainer.videosAreNonSkippable,
            createDataContainer.zenPublisherId,
            createDataContainer.brandSurveyId,
            createDataContainer.brandSurveyName,
            createDataContainer.showsFrequencyLimit,
            createDataContainer.strategyPlatform,
            null,
            grutUacCampaignWebService.getAdjustments(
                subjectUser,
                operator,
                directCampaignId,
                createDataContainer.advType
            ),
            isEcom = createDataContainer.isEcom,
            crr = createDataContainer.crr,
            feedId = createDataContainer.feedId,
            feedFilters = createDataContainer.feedFilters,
            showOfferStats = grutUacCampaignWebService.shouldShowOfferStats(subjectUser, createDataContainer.feedId),
            trackingParams = createDataContainer.trackingParams,
            cpmAssets = createDataContainer.cpmAssets,
            campaignMeasurers = createDataContainer.campaignMeasurers,
            uacBrandsafety = createDataContainer.uacBrandsafety,
            uacDisabledPlaces = createDataContainer.uacDisabledPlaces,
            isRecommendationsManagementEnabled = createDataContainer.isRecommendationsManagementEnabled,
            isPriceRecommendationsManagementEnabled = createDataContainer.isPriceRecommendationsManagementEnabled,
            relevanceMatchCategories = BaseUacCampaignWebService
                .getRelevanceMatchCategories(createDataContainer.relevanceMatch),
            showTitleAndBody = createDataContainer.showTitleAndBody,
            altAppStores = createDataContainer.altAppStores,
            bizLandingId = createDataContainer.bizLandingId,
            searchLift = createDataContainer.searchLift
        )
    }

    /**
     * Функция склеивает одинаковые медийные ассеты. Т.е. если в двух группах будет одинаковые images то
     * в базе грута это будет один ассет со своим уникальным id.
     * Функция так же убирает повторы внутри одной группы
     *
     * @param uacAdGroupBriefRequests список групповых заявок. Если null - заявка на кампанию
     * @param contents список всего медийного контента в кампании
     * @param uacMergeEqualsAssetsEnabled выключает склеивание и удаление повторов внутри одной группы
     *
     * @return возвращает мапу <заявка, медийный контент>. Для "заявка == null" - возвращает контент заявки на кампанию
     */
    private fun mergeMediaAssets(
        uacAdGroupBriefRequests: List<UacAdGroupBriefRequest?>,
        contents: Collection<Content>,
        uacMergeEqualsAssetsEnabled: Boolean,
    ): Map<UacAdGroupBriefRequest?, List<Content>> {
        val idToContent = contents
            .associateBy { it.id }
        val adGroupBriefToContent = uacAdGroupBriefRequests
            .associateBy({ it }) {
                // Добавляем по кампании
                if (it == null) contents.toList()
                // Добавляем по группе
                else it.contentIds
                    ?.mapNotNull { contentId -> idToContent[contentId] } ?: emptyList()
            }

        if (!uacMergeEqualsAssetsEnabled) {
            return adGroupBriefToContent
        }

        // Собираем мапу медийных контентов по уникальным ключевым полям (которые равны для одинакового контента)
        val imageSourceUrlToId = mutableMapOf<String, Content>()
        val imageHashToId = mutableMapOf<String, Content>()
        val videoSourceUrlToId = mutableMapOf<String, Content>()
        val videoMdsUrlToId = mutableMapOf<String, Content>()
        val html5SourceUrlToId = mutableMapOf<String, Content>()
        val html5MdsUrlToId = mutableMapOf<String, Content>()
        contents
            .forEach { content ->
                when (content.type) {
                    MediaType.IMAGE -> {
                        imageSourceUrlToId[content.sourceUrl] = content
                        content.directImageHash?.let { imageHash ->
                            imageHashToId[imageHash] = content
                        }
                    }
                    MediaType.VIDEO -> {
                        videoSourceUrlToId[content.sourceUrl] = content
                        content.mdsUrl?.let { url ->
                            videoMdsUrlToId[url] = content
                        }
                    }
                    MediaType.HTML5 -> {
                        html5SourceUrlToId[content.sourceUrl] = content
                        content.mdsUrl?.let { url ->
                            html5MdsUrlToId[url] = content
                        }
                    }
                    else -> throw IllegalArgumentException("Unsupported asset type: \"${content.type}\"")
                }
            }

        // Собираем для каждой группы уникальный контент из собранных выше мапперов
        return uacAdGroupBriefRequests
            .associateBy({ it }) { adGroupBriefToContent[it]!! }
            .mapValues { (_, contents) ->
                // Убираем повторы внутри одной группы через сеты
                val imageSourceUrl = mutableSetOf<String>()
                val imageImageHash = mutableSetOf<String>()
                val videoSourceUrl = mutableSetOf<String>()
                val videoMdsUrl = mutableSetOf<String>()
                val html5SourceUrl = mutableSetOf<String>()
                val html5MdsUrl = mutableSetOf<String>()
                contents
                    .mapNotNull { content ->
                        val sourceUrl = content.sourceUrl
                        when (content.type) {
                            MediaType.IMAGE -> {
                                if (!imageSourceUrl.contains(sourceUrl)
                                    && !imageImageHash.contains(content.directImageHash)
                                ) {
                                    imageSourceUrl.add(sourceUrl)
                                    content.directImageHash?.let { imageImageHash.add(it) }
                                    imageSourceUrlToId[sourceUrl] ?: imageHashToId[content.directImageHash]
                                } else null
                            }
                            MediaType.VIDEO -> {
                                if (!videoSourceUrl.contains(sourceUrl)
                                    && !videoMdsUrl.contains(content.mdsUrl)
                                ) {
                                    videoSourceUrl.add(sourceUrl)
                                    content.mdsUrl?.let { videoMdsUrl.add(it) }
                                    videoSourceUrlToId[sourceUrl] ?: videoMdsUrlToId[content.mdsUrl]
                                } else null
                            }
                            MediaType.HTML5 -> {
                                if (!html5SourceUrl.contains(sourceUrl)
                                    && !html5MdsUrl.contains(content.mdsUrl)
                                ) {
                                    html5SourceUrl.add(sourceUrl)
                                    content.mdsUrl?.let { html5MdsUrl.add(it) }
                                    html5SourceUrlToId[sourceUrl] ?: html5MdsUrlToId[content.mdsUrl]
                                } else null
                            }
                            else -> null
                        }
                    }
            }
    }

    /**
     * Функция склеивает одинаковые текстовые ассеты. Т.е. если в двух группах будет одинаковые title то
     * в базе грута это будет один ассет со своим уникальным id.
     * Функция так же убирает повторы внутри одной группы
     *
     * @param createDataContainer контейнер с данными по кампании/группам
     * @param uacAdGroupBriefRequests список групповых заявок. Если null - заявка на кампанию
     * @param uacMergeEqualsAssetsEnabled выключает склеивание и удаление повторов внутри одной группы
     *
     * @return возвращает мапу <заявка, текстовый контент>. Для "заявка == null" - возвращает контент заявки на кампанию
     */
    private fun mergeTextAssets(
        subjectUser: User,
        createDataContainer: UacModifyCampaignDataContainer,
        uacAdGroupBriefRequests: List<UacAdGroupBriefRequest?>,
        uacMergeEqualsAssetsEnabled: Boolean,
    ): Map<UacAdGroupBriefRequest?, List<AssetGrut>> {
        val clientId = subjectUser.clientId.asLong()

        val briefIdToTitles = uacAdGroupBriefRequests
            .associateBy({ it }) {
                if (it == null) createDataContainer.titles
                else it.titles
            }
        val briefIdToText = uacAdGroupBriefRequests
            .associateBy({ it }) {
                if (it == null) createDataContainer.texts
                else it.texts
            }
        val briefIdToSitelinks = uacAdGroupBriefRequests
            .associateBy({ it }) {
                if (it == null) createDataContainer.sitelinks
                else it.sitelinks
            }

        if (!uacMergeEqualsAssetsEnabled) {
            return uacAdGroupBriefRequests
                .associateBy({ it }) {
                    val titleToGrutAsset = getCreateTitlesSubRequests(briefIdToTitles[it], clientId) ?: listOf()
                    val textToGrutAsset = getCreateTextsSubRequests(briefIdToText[it], clientId) ?: listOf()
                    val sitelinkToGrutAsset =
                        getCreateSitelinksSubRequests(briefIdToSitelinks[it], clientId)?.values ?: listOf()
                    // Последовательность важна для дальнейшего расчета order
                    textToGrutAsset + titleToGrutAsset + sitelinkToGrutAsset
                }
        }

        // Собираем все текстовые ассеты из всех групп и создаем грутовые модели ассетов
        val titles = briefIdToTitles.values
            .filterNotNull()
            .flatten()
            .toSet()
        val texts = briefIdToText.values
            .filterNotNull()
            .flatten()
            .toSet()
        val sitelinks = briefIdToSitelinks.values
            .filterNotNull()
            .flatten()
            .toSet()
        val titleToGrutAsset = (getCreateTitlesSubRequests(titles, clientId) ?: listOf())
            .associateBy { it.title }
        val textToGrutAsset = (getCreateTextsSubRequests(texts, clientId) ?: listOf())
            .associateBy { it.text }
        val sitelinkToGrutAsset = getCreateSitelinksSubRequests(sitelinks, clientId) ?: mapOf()

        // Собираем для каждой группы уникальные ассеты из собранных выше грутовых моделей
        return uacAdGroupBriefRequests
            .associateBy({ it }) {
                val grutTitles = briefIdToTitles[it]
                    ?.distinct()
                    ?.mapNotNull { title -> titleToGrutAsset[title] }
                    ?: emptyList()
                val grutTexts = briefIdToText[it]
                    ?.distinct()
                    ?.mapNotNull { text -> textToGrutAsset[text] }
                    ?: emptyList()
                val grutSitelinks = briefIdToSitelinks[it]
                    ?.distinct()
                    ?.mapNotNull { sitelink -> sitelinkToGrutAsset[sitelink] }
                    ?: emptyList()
                // Последовательность важна для дальнейшего расчета order
                grutTexts + grutTitles + grutSitelinks
            }
    }

    private fun saveNonMediaAssets(
        grutAssets: List<AssetGrut>,
        existedGrutAssetIds: Set<Long>,
    ): Map<String, Int> {
        if (grutAssets.isEmpty()) {
            return emptyMap()
        }

        val gruAssetsToCreate = grutAssets
            .filterNot { existedGrutAssetIds.contains(it.id) }
        val response = grutApiService.assetGrutApi.createObjects(gruAssetsToCreate)
            .toSet()

        val gruAssetIdsReult = grutAssets
            .map { it.id }
            .filter { existedGrutAssetIds.contains(it) || response.contains(it) }

        return getAssetIdToOrder(
            gruAssetIdsReult,
            textsCount = grutAssets.filter { it.mediaType == MT_TEXT }.size,
            titlesCount = grutAssets.filter { it.mediaType == MT_TITLE }.size,
        )
    }

    private fun getCreateTextsSubRequests(
        texts: Collection<String>?,
        clientId: Long
    ): List<TextAssetGrut>? {
        return texts?.map {
            TextAssetGrut(
                id = generateRandomIdLong(),
                clientId = clientId,
                text = it
            )
        }
    }

    private fun getCreateTitlesSubRequests(
        titles: Collection<String>?,
        clientId: Long
    ): List<TitleAssetGrut>? {
        return titles?.map {
            TitleAssetGrut(
                id = generateRandomIdLong(),
                clientId = clientId,
                title = it
            )
        }
    }

    private fun getCreateSitelinksSubRequests(
        sitelinks: Collection<Sitelink>?,
        clientId: Long
    ): Map<Sitelink, SitelinkAssetGrut>? = sitelinks
        ?.associateBy({ it }) {
            SitelinkAssetGrut(
                id = generateRandomIdLong(),
                clientId = clientId,
                title = it.title,
                href = it.href,
                description = it.description
            )
        }


    private fun getAssetIdToOrder(
        ids: List<Long>,
        textsCount: Int,
        titlesCount: Int,
    ): Map<String, Int> {
        return ids.mapIndexed { index, id ->
            val order = when {
                index < textsCount -> {
                    index
                }
                index < textsCount + titlesCount -> {
                    index - textsCount
                }
                else -> {
                    index - textsCount - titlesCount
                }
            }
            id.toIdString() to order
        }.toMap()
    }

    override fun getContents(contentIds: Collection<String>): List<Content> {
        val contentIdToOrder = contentIds.mapIndexed { order, id ->
            id to order
        }.toMap()

        val assetContainer = grutUacContentService.getAssetContainer(contentIds.toSet(), contentIdToOrder)

        return (assetContainer.images + assetContainer.videos + assetContainer.html5s).sortedBy { it.order }
            .map { it.asset }
            .let { grutUacContentService.fillMediaContentsFromAssets(it) }
    }
}
