package ru.yandex.direct.api.v5.entity.campaigns.converter

import com.yandex.direct.api.v5.campaigns.CampaignUpdateItem
import com.yandex.direct.api.v5.campaigns.TextCampaignNetworkStrategy
import com.yandex.direct.api.v5.campaigns.TextCampaignNetworkStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.TextCampaignSearchStrategy
import com.yandex.direct.api.v5.campaigns.TextCampaignSearchStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.TextCampaignSetting
import com.yandex.direct.api.v5.campaigns.TextCampaignSettingsEnum
import com.yandex.direct.api.v5.campaigns.TextCampaignStrategy
import com.yandex.direct.api.v5.general.YesNoEnum
import org.apache.commons.beanutils.BeanUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import ru.yandex.direct.api.v5.common.buildModelChanges
import ru.yandex.direct.api.v5.common.processJaxbElement
import ru.yandex.direct.api.v5.entity.campaigns.container.UpdateCampaignsConvertedRequest
import ru.yandex.direct.core.entity.campaign.model.BaseCampaign
import ru.yandex.direct.core.entity.campaign.model.CampaignWithBannerHrefParams
import ru.yandex.direct.core.entity.campaign.model.CampaignWithBroadMatch
import ru.yandex.direct.core.entity.campaign.model.CampaignWithPackageStrategy
import ru.yandex.direct.core.entity.campaign.model.CampaignWithTimeTargeting
import ru.yandex.direct.core.entity.campaign.model.CommonCampaign
import ru.yandex.direct.core.entity.campaign.model.TextCampaign
import ru.yandex.direct.core.entity.feature.service.FeatureService
import ru.yandex.direct.core.entity.timetarget.model.GeoTimezone
import ru.yandex.direct.dbutil.model.UidAndClientId
import ru.yandex.direct.feature.FeatureName

@Component
class TextCampaignUpdateItemConverter @Autowired constructor(
    private val featureService: FeatureService
) : CampaignUpdateItemConverter<TextCampaign> {

    override fun convert(
        item: CampaignUpdateItem,
        uidAndClientId: UidAndClientId,
        timeZones: Map<String, GeoTimezone>,
        knownSsps: Set<String>,
        campaignFromDb: BaseCampaign?
    ): UpdateCampaignsConvertedRequest<TextCampaign> {
        check(campaignFromDb == null || campaignFromDb is TextCampaign)
        val textItem = item.textCampaign
        val settings = textItem?.settings.orEmpty()
            .associateBy(TextCampaignSetting::getOption) { it.value == YesNoEnum.YES }

        val modelChanges = buildModelChanges<TextCampaign>(item.id) {
            val oldTimeTarget = campaignFromDb?.let { (it as CampaignWithTimeTargeting).timeTarget }
            val oldBroadMatch = campaignFromDb?.let { (it as CampaignWithBroadMatch).broadMatch }
            val oldCommonCampaign = campaignFromDb?.let { (it as CommonCampaign) }
            processCommonFields(item, timeZones, oldTimeTarget, oldCommonCampaign)
            processAttributionModel(textItem?.attributionModel)
            processMetrikaCounters(textItem?.counterIds)
            processDailyBudget(item)
            processJaxbElement(item.negativeKeywords, TextCampaign.MINUS_KEYWORDS) { it!!.items }
            processExcludedSites(item, knownSsps)
            processCheckPositionInterval(item)
            processBroadMatch(textItem?.relevantKeywords, oldBroadMatch)
            processPriorityGoals(textItem?.priorityGoals)
            processJaxbElement(textItem?.strategyId, CampaignWithPackageStrategy.STRATEGY_ID)
            processJaxbElement(textItem?.trackingParams, CampaignWithBannerHrefParams.BANNER_HREF_PARAMS)

            val apiStrategy = textItem?.biddingStrategy
            val newStrategy = apiStrategy?.let {
                val newApiStrategy = mergeOldAndNewStrategies(it, campaignFromDb as TextCampaign)
                TextCampaignStrategyConverter.toCampaignStrategy(newApiStrategy)
            }
            processStrategy(newStrategy)
            processContextLimit(apiStrategy?.network?.toContextLimit())

            // ENABLE_RELATED_KEYWORDS, ENABLE_AUTOFOCUS, ENABLE_BEHAVIORAL_TARGETING - deprecated
            processCommonSettings(
                enableCompanyInfo = settings[TextCampaignSettingsEnum.ENABLE_COMPANY_INFO],
                // enableCpcHold перезапишется в ядре, если кампания - автобюджетная
                // https://a.yandex-team.ru/arc_vcs/direct/core/src/main/java/ru/yandex/direct/core/entity/campaign/service/type/update/CampaignWithNetworkSettingsUpdateOperationSupport.java?rev=r7408600#L42
                enableCpcHold = settings[TextCampaignSettingsEnum.MAINTAIN_NETWORK_CPC],
                hasTitleSubstitution = settings[TextCampaignSettingsEnum.ENABLE_EXTENDED_AD_TITLE],
                isOrderPhraseLengthPrecedenceEnabled = settings[TextCampaignSettingsEnum.CAMPAIGN_EXACT_PHRASE_MATCHING_ENABLED],
            )
            processHasAddOpenstatTagToUrl(settings[TextCampaignSettingsEnum.ADD_OPENSTAT_TAG])
            processHasAddMetrikaTagToUrl(settings[TextCampaignSettingsEnum.ADD_METRICA_TAG])
            processHasExtendedGeoTargeting(settings[TextCampaignSettingsEnum.ENABLE_AREA_OF_INTEREST_TARGETING])

            val advancedGeoTargeting = featureService.isEnabledForClientId(
                uidAndClientId.clientId,
                FeatureName.ADVANCED_GEOTARGETING
            )
            if (advancedGeoTargeting) {
                processUseCurrentRegion(settings[TextCampaignSettingsEnum.ENABLE_CURRENT_AREA_TARGETING])
                processUseRegularRegion(settings[TextCampaignSettingsEnum.ENABLE_REGULAR_AREA_TARGETING])
            }
            processHasSiteMonitoring(settings[TextCampaignSettingsEnum.ENABLE_SITE_MONITORING])
            processExcludePausedCompetingAds(settings[TextCampaignSettingsEnum.EXCLUDE_PAUSED_COMPETING_ADS])
            processFavoriteForIds(uidAndClientId.uid, settings[TextCampaignSettingsEnum.ADD_TO_FAVORITES])
        }

        return UpdateCampaignsConvertedRequest(
            modelChanges,
            requireServicing = settings[TextCampaignSettingsEnum.REQUIRE_SERVICING]
        )
    }

    companion object {
        /**
         * Мёржит apiStrategy с данными стратегии из oldCampaign, при условии, что biddingStrategyType одинаковыйи
         * возвращает объединённую стратегию
         */
        fun mergeOldAndNewStrategies(
            apiStrategy: TextCampaignStrategy?,
            campaignFromDb: TextCampaign
        ): TextCampaignStrategy {
            val result = TextCampaignStrategy().apply {
                if (apiStrategy != null) {
                    BeanUtils.copyProperties(this, apiStrategy)
                }
            }
            val oldStrategy = toTextCampaignExternalStrategy(campaignFromDb.strategy, campaignFromDb.contextLimit)
            if (result.search == null) {
                result.search = oldStrategy.search
            } else if (result.search.biddingStrategyType == oldStrategy.search.biddingStrategyType) {
                copyProperties(result.search, oldStrategy.search)
                result.search = oldStrategy.search
            }
            if (result.network == null) {
                result.network = oldStrategy.network
            } else if (result.network.biddingStrategyType == oldStrategy.network.biddingStrategyType) {
                copyProperties(result.network, oldStrategy.network)
                result.network = oldStrategy.network
            }
            return result
        }

        @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
        private fun copyProperties(source: TextCampaignSearchStrategy, dest: TextCampaignSearchStrategy) {
            check(source.biddingStrategyType == dest.biddingStrategyType)
            when (source.biddingStrategyType) {
                TextCampaignSearchStrategyTypeEnum.AVERAGE_CPC ->
                    copyPropertiesExceptNull(source.averageCpc, dest.averageCpc)
                TextCampaignSearchStrategyTypeEnum.AVERAGE_CPA ->
                    copyPropertiesExceptNull(source.averageCpa, dest.averageCpa)
                TextCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION ->
                    copyPropertiesExceptNull(source.payForConversion, dest.payForConversion)
                TextCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE ->
                    copyPropertiesExceptNull(source.wbMaximumConversionRate, dest.wbMaximumConversionRate)
                TextCampaignSearchStrategyTypeEnum.HIGHEST_POSITION -> {
                    // DEFAULT_STRATEGY_NAME, no properties
                }
                TextCampaignSearchStrategyTypeEnum.IMPRESSIONS_BELOW_SEARCH -> {
                    // not used
                }
                TextCampaignSearchStrategyTypeEnum.SERVING_OFF -> {
                    // no properties
                }
                TextCampaignSearchStrategyTypeEnum.UNKNOWN -> {
                    // not used
                }
                TextCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CLICKS ->
                    copyPropertiesExceptNull(source.wbMaximumClicks, dest.wbMaximumClicks)
                TextCampaignSearchStrategyTypeEnum.WEEKLY_CLICK_PACKAGE ->
                    copyPropertiesExceptNull(source.weeklyClickPackage, dest.weeklyClickPackage)
                TextCampaignSearchStrategyTypeEnum.AVERAGE_ROI ->
                    copyPropertiesExceptNull(source.averageRoi, dest.averageRoi)
                TextCampaignSearchStrategyTypeEnum.AVERAGE_CRR ->
                    copyPropertiesExceptNull(source.averageCrr, dest.averageCrr)
                TextCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION_CRR ->
                    copyPropertiesExceptNull(source.payForConversionCrr, dest.payForConversionCrr)
            }
        }

        @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
        private fun copyProperties(source: TextCampaignNetworkStrategy, dest: TextCampaignNetworkStrategy) {
            check(source.biddingStrategyType == dest.biddingStrategyType)
            when (source.biddingStrategyType) {
                TextCampaignNetworkStrategyTypeEnum.AVERAGE_CPC ->
                    copyPropertiesExceptNull(source.averageCpc, dest.averageCpc)
                TextCampaignNetworkStrategyTypeEnum.AVERAGE_CPA ->
                    copyPropertiesExceptNull(source.averageCpa, dest.averageCpa)
                TextCampaignNetworkStrategyTypeEnum.PAY_FOR_CONVERSION ->
                    copyPropertiesExceptNull(source.payForConversion, dest.payForConversion)
                TextCampaignNetworkStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE ->
                    copyPropertiesExceptNull(source.wbMaximumConversionRate, dest.wbMaximumConversionRate)
                TextCampaignNetworkStrategyTypeEnum.MAXIMUM_COVERAGE -> {
                    // DEFAULT_STRATEGY_NAME, no properties
                }
                TextCampaignNetworkStrategyTypeEnum.NETWORK_DEFAULT -> {
                    copyPropertiesExceptNull(source.networkDefault, dest.networkDefault)
                }
                TextCampaignNetworkStrategyTypeEnum.SERVING_OFF -> {
                    // no properties
                }
                TextCampaignNetworkStrategyTypeEnum.UNKNOWN -> {
                    // not used
                }
                TextCampaignNetworkStrategyTypeEnum.WB_MAXIMUM_CLICKS ->
                    copyPropertiesExceptNull(source.wbMaximumClicks, dest.wbMaximumClicks)
                TextCampaignNetworkStrategyTypeEnum.WEEKLY_CLICK_PACKAGE ->
                    copyPropertiesExceptNull(source.weeklyClickPackage, dest.weeklyClickPackage)
                TextCampaignNetworkStrategyTypeEnum.AVERAGE_ROI ->
                    copyPropertiesExceptNull(source.averageRoi, dest.averageRoi)
                TextCampaignNetworkStrategyTypeEnum.AVERAGE_CRR ->
                    copyPropertiesExceptNull(source.averageCrr, dest.averageCrr)
                TextCampaignNetworkStrategyTypeEnum.PAY_FOR_CONVERSION_CRR ->
                    copyPropertiesExceptNull(source.payForConversionCrr, dest.payForConversionCrr)
            }
        }
    }
}
