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

import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignNetworkStrategy
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignNetworkStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignSearchStrategy
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignSearchStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignSetting
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignStrategy
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignUpdateItem
import com.yandex.direct.api.v5.campaigns.PlacementType
import ru.yandex.direct.api.v5.entity.campaigns.CampaignDefectTypes
import ru.yandex.direct.api.v5.entity.campaigns.converter.toCorePlacementTypes
import ru.yandex.direct.api.v5.entity.campaigns.converter.toDynamicTextCampaignExternalStrategy
import ru.yandex.direct.api.v5.validation.DefectType
import ru.yandex.direct.core.entity.campaign.model.DynamicCampaign
import ru.yandex.direct.validation.builder.ItemValidationBuilder
import ru.yandex.direct.validation.builder.When
import ru.yandex.direct.validation.result.ValidationResult
import java.util.function.Function

object DynamicTextCampaignUpdateRequestValidator {

    private val STRUCTURE_GETTER_BY_SEARCH_STRATEGY_TYPE =
        mapOf<DynamicTextCampaignSearchStrategyTypeEnum?, Function<DynamicTextCampaignSearchStrategy, Any>?>(
            DynamicTextCampaignSearchStrategyTypeEnum.AVERAGE_CPA to
                Function { it.averageCpa },
            DynamicTextCampaignSearchStrategyTypeEnum.AVERAGE_CPC to
                Function { it.averageCpc },
            DynamicTextCampaignSearchStrategyTypeEnum.AVERAGE_CRR to
                Function { it.averageCrr },
            DynamicTextCampaignSearchStrategyTypeEnum.AVERAGE_ROI to
                Function { it.averageRoi },
            DynamicTextCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION to
                Function { it.payForConversion },
            DynamicTextCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION_CRR to
                Function { it.payForConversionCrr },
            DynamicTextCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CLICKS to
                Function { it.wbMaximumClicks },
            DynamicTextCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE to
                Function { it.wbMaximumConversionRate },
            DynamicTextCampaignSearchStrategyTypeEnum.WEEKLY_CLICK_PACKAGE to
                Function { it.weeklyClickPackage })

    private val STRATEGY_TYPES_SEARCH_WITH_OPTIONAL_STRUCTURE =
        setOf<DynamicTextCampaignSearchStrategyTypeEnum>()

    // ДО не может быть в сетях, потому кроме SERVING_OFF ничего быть не может
    private val STRUCTURE_GETTER_BY_NETWORK_STRATEGY_TYPE =
        mapOf<DynamicTextCampaignNetworkStrategyTypeEnum, Function<DynamicTextCampaignNetworkStrategy, Any>>()

    private val STRATEGY_TYPES_NETWORK_WITH_OPTIONAL_STRUCTURE =
        setOf<DynamicTextCampaignNetworkStrategyTypeEnum>()

    fun validateDynamicTextCampaign(
        item: DynamicTextCampaignUpdateItem,
        campaignFromDb: DynamicCampaign?
    ): ValidationResult<DynamicTextCampaignUpdateItem, DefectType> {
        val vb = ItemValidationBuilder.of<DynamicTextCampaignUpdateItem, DefectType>(item)
        vb.apply {
            item(item.biddingStrategy, "BiddingStrategy")
                .checkBy(
                    { validateCampaignStrategy(it, campaignFromDb!!) },
                    When.notNull()
                )
            item(item.placementTypes, "PlacementTypes")
                .check(CampaignsCommonRequestValidator::duplicatesInPlacementTypes)
                .check({ placementTypesAllDisabled(it, campaignFromDb!!) }, When.notNull())
            list(item.settings, "Settings")
                .weakCheckEach { setting: DynamicTextCampaignSetting ->
                    CampaignsCommonRequestValidator.settingIsSupported(setting.option)
                }
            item(item.priorityGoals?.value, "PriorityGoals")
                .check(CampaignsUpdateRequestValidator::priorityGoalsNotSupportedOperation)
        }
        return vb.result
    }

    private fun validateCampaignStrategy(
        strategy: DynamicTextCampaignStrategy,
        campaignFromDb: DynamicCampaign
    ): ValidationResult<DynamicTextCampaignStrategy, DefectType> {
        val oldStrategy = toDynamicTextCampaignExternalStrategy(campaignFromDb.strategy)
        val vb = ItemValidationBuilder.of<DynamicTextCampaignStrategy, DefectType>(strategy)
        vb.apply {
            item(strategy.search, "Search")
                .check(::validateSearchStrategyConsistent, When.notNull())
                .check(
                    { validateSearchStrategyTypeIsSupported(it) },
                    When.isValidAnd(When.notNull())
                )
            item(strategy.network, "Network")
                .check(::validateNetworkStrategyConsistent, When.notNull())
                .check(
                    { validateNetworkStrategyTypeIsSupported(it) },
                    When.isValidAnd(When.notNull())
                )
            check({ validateStrategyTypesAreCompatible(it, oldStrategy) }, When.isValid())
            check(::goalIdNotSet)
        }
        return vb.result
    }

    private fun validateSearchStrategyConsistent(
        searchStrategy: DynamicTextCampaignSearchStrategy
    ) =
        CampaignsRequestValidator.validateStrategyConsistent(
            searchStrategy,
            searchStrategy.biddingStrategyType,
            STRUCTURE_GETTER_BY_SEARCH_STRATEGY_TYPE,
            STRATEGY_TYPES_SEARCH_WITH_OPTIONAL_STRUCTURE,
            true
        )

    private fun validateNetworkStrategyConsistent(
        networkStrategy: DynamicTextCampaignNetworkStrategy
    ) =
        CampaignsRequestValidator.validateStrategyConsistent(
            networkStrategy,
            networkStrategy.biddingStrategyType,
            STRUCTURE_GETTER_BY_NETWORK_STRATEGY_TYPE,
            STRATEGY_TYPES_NETWORK_WITH_OPTIONAL_STRUCTURE,
            false
        )

    private fun validateSearchStrategyTypeIsSupported(
        searchStrategy: DynamicTextCampaignSearchStrategy
    ) = when (searchStrategy.biddingStrategyType) {
        DynamicTextCampaignSearchStrategyTypeEnum.IMPRESSIONS_BELOW_SEARCH ->
            CampaignDefectTypes.impressionsBelowSearchStrategyIsNotSupported()
        DynamicTextCampaignSearchStrategyTypeEnum.WEEKLY_CLICK_PACKAGE ->
            CampaignDefectTypes.weeklyClickPackageNotSupported()
        else ->
            null
    }

    private fun validateNetworkStrategyTypeIsSupported(
        networkStrategy: DynamicTextCampaignNetworkStrategy
    ) = when (networkStrategy.biddingStrategyType) {
        DynamicTextCampaignNetworkStrategyTypeEnum.WEEKLY_CLICK_PACKAGE ->
            CampaignDefectTypes.weeklyClickPackageNotSupported()
        else ->
            null
    }

    private fun validateStrategyTypesAreCompatible(
        strategy: DynamicTextCampaignStrategy,
        oldStrategy: DynamicTextCampaignStrategy
    ): DefectType? {
        val searchStrategyType = strategy.search?.biddingStrategyType ?: oldStrategy.search.biddingStrategyType!!
        val networkStrategyType = strategy.network?.biddingStrategyType ?: oldStrategy.network.biddingStrategyType!!
        val compatibleNetworkTypes = getNetworkStrategyTypesCompatibleWith(searchStrategyType)
        return if (!compatibleNetworkTypes.contains(networkStrategyType)) {
            CampaignDefectTypes.strategiesAreNotCompatible()
        } else null
    }

    private fun goalIdNotSet(strategy: DynamicTextCampaignStrategy): DefectType? {
        val searchStrategy: DynamicTextCampaignSearchStrategy? = strategy.search
        val networkStrategy: DynamicTextCampaignNetworkStrategy? = strategy.network
        val notOk =
            networkStrategy?.biddingStrategyType == DynamicTextCampaignNetworkStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE
                && networkStrategy.wbMaximumConversionRate != null
                && networkStrategy.wbMaximumConversionRate.goalId == null
                || searchStrategy?.biddingStrategyType == DynamicTextCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE
                && searchStrategy.wbMaximumConversionRate != null
                && searchStrategy.wbMaximumConversionRate.goalId == null
        return if (notOk) CampaignDefectTypes.goalIdNotSet() else null
    }

    /**
     * На основе https://a.yandex-team.ru/arc_vcs/direct/perl/api/services/v5/API/Service/Campaigns.pm?rev=r9133034#L2507
     * Если передать пустой массив в ядро - это будет считаться, как все включено (YES)
     * Потому валидируем ситуацию, когда пользователь указал в запросе непустой PlacementTypes,
     * который выключил все включенное в PlacementTypes из базы - и получился пустой список со смыслом "все выключено".
     * Провалидировать пользовательский ввод, когда юзер выключает все placementTypes через = NO можно только в уровне API
     */
    private fun placementTypesAllDisabled(
        placementTypes: List<PlacementType>,
        oldPlacementTypes: DynamicCampaign
    ): DefectType? {
        val newCorePlacementTypes = toCorePlacementTypes(placementTypes, oldPlacementTypes.placementTypes)
        val notOk = placementTypes.isNotEmpty() && newCorePlacementTypes.isEmpty()
        return if (notOk) CampaignDefectTypes.placementTypesAllDisabled() else null
    }
}
