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

import com.yandex.direct.api.v5.campaigns.SmartCampaignNetworkStrategy
import com.yandex.direct.api.v5.campaigns.SmartCampaignNetworkStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.SmartCampaignSearchStrategy
import com.yandex.direct.api.v5.campaigns.SmartCampaignSearchStrategyTypeEnum
import com.yandex.direct.api.v5.campaigns.SmartCampaignStrategy
import com.yandex.direct.api.v5.campaigns.SmartCampaignUpdateItem
import ru.yandex.direct.api.v5.entity.campaigns.CampaignDefectTypes
import ru.yandex.direct.api.v5.entity.campaigns.converter.toContextLimit
import ru.yandex.direct.api.v5.entity.campaigns.converter.toSmartCampaignExternalStrategy
import ru.yandex.direct.api.v5.entity.campaigns.validation.CampaignsRequestValidator.validateNetworkDefaultLimitPercent
import ru.yandex.direct.api.v5.validation.DefectType
import ru.yandex.direct.api.v5.validation.validateObject
import ru.yandex.direct.core.entity.campaign.model.SmartCampaign
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 SmartCampaignUpdateRequestValidator {

    private val STRUCTURE_GETTER_BY_SEARCH_STRATEGY_TYPE =
        mapOf<SmartCampaignSearchStrategyTypeEnum, Function<SmartCampaignSearchStrategy, Any>>(
            SmartCampaignSearchStrategyTypeEnum.AVERAGE_CPC_PER_CAMPAIGN to
                Function { it.averageCpcPerCampaign },
            SmartCampaignSearchStrategyTypeEnum.AVERAGE_CPC_PER_FILTER to
                Function { it.averageCpcPerFilter },
            SmartCampaignSearchStrategyTypeEnum.AVERAGE_CPA_PER_CAMPAIGN to
                Function { it.averageCpaPerCampaign },
            SmartCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION_PER_CAMPAIGN to
                Function { it.payForConversionPerCampaign },
            SmartCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION_PER_FILTER to
                Function { it.payForConversionPerFilter },
            SmartCampaignSearchStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE to
                Function { it.wbMaximumConversionRate },
            SmartCampaignSearchStrategyTypeEnum.PAY_FOR_CONVERSION_CRR to
                Function { it.payForConversionCrr },
            SmartCampaignSearchStrategyTypeEnum.AVERAGE_ROI to
                Function { it.averageRoi },
            SmartCampaignSearchStrategyTypeEnum.AVERAGE_CRR to
                Function { it.averageCrr })

    private val STRATEGY_TYPES_SEARCH_WITH_OPTIONAL_STRUCTURE =
        setOf<SmartCampaignSearchStrategyTypeEnum>()

    private val STRUCTURE_GETTER_BY_NETWORK_STRATEGY_TYPE =
        mapOf<SmartCampaignNetworkStrategyTypeEnum?, Function<SmartCampaignNetworkStrategy, Any>?>(
            SmartCampaignNetworkStrategyTypeEnum.NETWORK_DEFAULT to
                Function { it.networkDefault },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_CPC_PER_CAMPAIGN to
                Function { it.averageCpcPerCampaign },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_CPC_PER_FILTER to
                Function { it.averageCpcPerFilter },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_CPA_PER_CAMPAIGN to
                Function { it.averageCpaPerCampaign },
            SmartCampaignNetworkStrategyTypeEnum.PAY_FOR_CONVERSION_PER_CAMPAIGN to
                Function { it.payForConversionPerCampaign },
            SmartCampaignNetworkStrategyTypeEnum.PAY_FOR_CONVERSION_PER_FILTER to
                Function { it.payForConversionPerFilter },
            SmartCampaignNetworkStrategyTypeEnum.WB_MAXIMUM_CONVERSION_RATE to
                Function { it.wbMaximumConversionRate },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_CPA_PER_FILTER to
                Function { it.averageCpaPerFilter },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_ROI to
                Function { it.averageRoi },
            SmartCampaignNetworkStrategyTypeEnum.AVERAGE_CRR to
                Function { it.averageCrr })

    private val STRATEGY_TYPES_NETWORK_WITH_OPTIONAL_STRUCTURE =
        setOf(SmartCampaignNetworkStrategyTypeEnum.NETWORK_DEFAULT)

    fun validateSmartCampaign(
        item: SmartCampaignUpdateItem,
        campaignFromDb: SmartCampaign?,
    ): ValidationResult<SmartCampaignUpdateItem, DefectType> {
        val vb = ItemValidationBuilder.of<SmartCampaignUpdateItem, DefectType>(item)
        vb.apply {
            item(item.biddingStrategy, "BiddingStrategy")
                .checkBy({ validateCampaignStrategy(it, campaignFromDb!!) }, When.notNull())
            item(item.priorityGoals?.value, "PriorityGoals")
                .check(CampaignsUpdateRequestValidator::priorityGoalsNotSupportedOperation)
        }
        return vb.result
    }

    private fun validateCampaignStrategy(
        strategy: SmartCampaignStrategy,
        campaignFromDb: SmartCampaign,
    ): ValidationResult<SmartCampaignStrategy, DefectType> =
        validateObject(strategy) {
            val oldStrategy = toSmartCampaignExternalStrategy(campaignFromDb.strategy, campaignFromDb.contextLimit)
            item(strategy.search, "Search")
                .check(::validateSearchStrategyConsistent, When.notNull())
            item(strategy.network, "Network")
                .check(::validateNetworkStrategyConsistent, When.notNull())
                .check(
                    { validateNetworkDefaultLimitPercent(it.toContextLimit()) },
                    When.isValid()
                )
            check({ validateStrategyTypesAreCompatible(it, oldStrategy) }, When.isValid())
            weakCheck({ limitPercentIsConsistentWithStrategy(it, campaignFromDb) }, When.isValid())
        }

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

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

    private fun validateStrategyTypesAreCompatible(
        strategy: SmartCampaignStrategy,
        oldStrategy: SmartCampaignStrategy
    ): 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
    }
}
