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

import org.springframework.stereotype.Service
import ru.yandex.direct.core.entity.campaign.converter.CampaignConverter.toCampaignTypeWithCounterIds
import ru.yandex.direct.core.entity.campaign.model.CampaignMetatype
import ru.yandex.direct.core.entity.campaign.model.CampaignSource
import ru.yandex.direct.core.entity.campaign.model.CampaignType
import ru.yandex.direct.core.entity.campaign.model.DbStrategy
import ru.yandex.direct.core.entity.campaign.model.DynamicCampaign
import ru.yandex.direct.core.entity.campaign.model.SmartCampaign
import ru.yandex.direct.core.entity.campaign.model.StrategyData
import ru.yandex.direct.core.entity.campaign.model.StrategyName
import ru.yandex.direct.core.entity.campaign.model.TextCampaign
import ru.yandex.direct.core.entity.campaign.service.CampMetrikaCountersService
import ru.yandex.direct.core.entity.feature.service.FeatureService
import ru.yandex.direct.core.entity.metrika.container.CampaignTypeWithCounterIds
import ru.yandex.direct.core.entity.metrika.service.MetrikaGoalsService
import ru.yandex.direct.core.entity.metrika.service.campaigngoals.CampaignGoalsService
import ru.yandex.direct.core.entity.mobileapp.model.MobileGoalConversions
import ru.yandex.direct.core.entity.mobileapp.repository.MobileAppConversionStatisticRepository
import ru.yandex.direct.core.entity.retargeting.model.Goal
import ru.yandex.direct.core.entity.retargeting.model.MetrikaCounterGoalType
import ru.yandex.direct.core.entity.uac.appendIf
import ru.yandex.direct.core.entity.uac.model.AppInfo
import ru.yandex.direct.core.entity.uac.model.TargetType
import ru.yandex.direct.core.entity.uac.model.TrackingUrl
import ru.yandex.direct.core.entity.uac.model.UacGoalType
import ru.yandex.direct.core.entity.uac.model.UacStrategyName
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbTrackerUrlStatRepository
import ru.yandex.direct.core.entity.uac.repository.ydb.model.TrackerAppEvent
import ru.yandex.direct.core.entity.uac.repository.ydb.model.UacTrackerUrlStat
import ru.yandex.direct.dbutil.model.ClientId
import ru.yandex.direct.feature.FeatureName
import ru.yandex.direct.utils.model.UrlParts
import ru.yandex.direct.web.entity.uac.converter.UacTextCampaignConverter.getCrr
import ru.yandex.direct.web.entity.uac.converter.UacTextCampaignConverter.getGoalId
import ru.yandex.direct.web.entity.uac.converter.UacTextCampaignConverter.isCpaStrategy
import ru.yandex.direct.web.entity.uac.converter.UacTextCampaignConverter.isCrrStrategy
import ru.yandex.direct.web.entity.uac.converter.UacTextCampaignConverter.toStrategyDataContainer
import ru.yandex.direct.web.entity.uac.model.CreateCampaignInternalRequest
import ru.yandex.direct.web.entity.uac.model.PatchCampaignInternalRequest
import ru.yandex.direct.web.entity.uac.model.UacAvailableStrategyGoals
import ru.yandex.direct.web.entity.uac.model.UacAvailableStrategySettings
import ru.yandex.direct.web.entity.uac.model.UacGoalInfo
import ru.yandex.direct.web.entity.uac.model.UacStrategyDataContainer

@Service
class UacGoalsService(
    private val campMetrikaCountersService: CampMetrikaCountersService,
    private val campaignGoalsService: CampaignGoalsService,
    private val metrikaGoalsService: MetrikaGoalsService,
    private val featureService: FeatureService,
    private val uacYdbTrackerUrlStatRepository: UacYdbTrackerUrlStatRepository,
    private val mobileAppConversionStatisticRepository: MobileAppConversionStatisticRepository,
    private val uacPropertiesService: UacPropertiesService
) {
    fun getGoals(
        operatorUid: Long,
        clientId: ClientId,
        counterIds: Collection<Long>
    ): List<UacGoalInfo> {
        return metrikaGoalsService.getMetrikaGoalsByCounter(operatorUid, clientId, counterIds, null, null)
            .map {
                UacGoalInfo(
                    counterId = it.counterId,
                    goalId = it.id,
                    goalType = getGoalType(it.metrikaCounterGoalType)
                )
            }
            .toList()
    }

    private fun getGoalType(goalType: MetrikaCounterGoalType) = when (goalType) {
        MetrikaCounterGoalType.ECOMMERCE,
        MetrikaCounterGoalType.E_CART,
        MetrikaCounterGoalType.E_PURCHASE,
        MetrikaCounterGoalType.A_CART,
        MetrikaCounterGoalType.A_PURCHASE -> UacGoalType.ECOMMERCE
        MetrikaCounterGoalType.CALL,
        MetrikaCounterGoalType.CONDITIONAL_CALL -> UacGoalType.CALL
        MetrikaCounterGoalType.OFFLINE -> UacGoalType.OFFLINE
        else -> UacGoalType.OTHER
    }

    fun getAvailableCounters(clientId: ClientId, counterIds: List<Long>): Set<Int> {
        val availableCounterIds = campMetrikaCountersService.getAvailableCounterIdsByClientId(clientId, counterIds)
        val unavailableCounterIds = counterIds
            .filterNot { o: Long? -> availableCounterIds.contains(o) }
            .toSet()
        val allowedUnavailableCounterIds =
            campMetrikaCountersService.getAllowedInaccessibleCounterIds(unavailableCounterIds)
        return (allowedUnavailableCounterIds + availableCounterIds)
            .map { it.toInt() }
            .toSet()
    }

    fun getAvailableGoalsForEcomCampaign(operatorUid: Long, clientId: ClientId, request: CreateCampaignInternalRequest): Map<Long, Goal> {
        val strategy = getStrategy(toStrategyDataContainer(request))
        return getAvailableGoalsForEcomCampaign(
                operatorUid,
                clientId,
                request.counters?.map { it.toLong() } ?: listOf(),
                strategy)
    }

    fun getAvailableGoalsForEcomCampaign(operatorUid: Long, clientId: ClientId, request: PatchCampaignInternalRequest): Map<Long, Goal> {
        val strategy = getStrategy(toStrategyDataContainer(request))
        return getAvailableGoalsForEcomCampaign(
                operatorUid,
                clientId,
                request.counters?.map { it.toLong() } ?: listOf(),
                strategy)
    }

    private fun removeParamsAndAnchor(urlParts: UrlParts) =
        urlParts.toBuilder()
            .withParameters(null)
            .withAnchor(null)
            .build()

    private fun isCrrStrategyAvailable(
        target: TargetType,
        event: TrackerAppEvent,
        trackerUrlStat: UacTrackerUrlStat?,
        mobileAppStat: Map<Long, MobileGoalConversions>?
    ): Boolean {
        val trackerUrlConversions: Long = trackerUrlStat?.conversionsByEvent?.get(event) ?: 0
        val mobileAppConversions: Long = mobileAppStat?.get(target.goalId)?.hasRevenueConversions ?: 0
        return !(trackerUrlConversions < uacPropertiesService.crrTrackerThreshold ||
                mobileAppConversions < uacPropertiesService.crrMobileAppThreshold)
    }

    private fun getAvailableTargets(
        trackerUrlStat: UacTrackerUrlStat?,
        mobileAppConversions: Map<Long, MobileGoalConversions>?,
        isOnlyCpcSkadNetwork: Boolean,
        isCrrEnabled: Boolean
    ): Map<TargetType, Set<UacStrategyName>> {
        if (isOnlyCpcSkadNetwork) {
            return mapOf(
                TargetType.CPC to setOf(UacStrategyName.AUTOBUDGET_AVG_CLICK)
            )
        }

        val goalsWithCrr = mapOf(
            TargetType.PAYMENT to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA)
                .appendIf(UacStrategyName.AUTOBUDGET_CRR,
                    isCrrEnabled &&
                    isCrrStrategyAvailable(TargetType.PAYMENT, TrackerAppEvent.PURCHASED, trackerUrlStat, mobileAppConversions)
                ),
            TargetType.BUY to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA)
                .appendIf(UacStrategyName.AUTOBUDGET_CRR,
                    isCrrEnabled &&
                    isCrrStrategyAvailable(TargetType.BUY, TrackerAppEvent.PURCHASED, trackerUrlStat, mobileAppConversions)
                ),
            TargetType.USER_1 to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA)
                .appendIf(UacStrategyName.AUTOBUDGET_CRR,
                    isCrrEnabled &&
                    isCrrStrategyAvailable(TargetType.USER_1, TrackerAppEvent.EVENT_1, trackerUrlStat, mobileAppConversions)
                ),
            TargetType.USER_2 to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA)
                .appendIf(UacStrategyName.AUTOBUDGET_CRR,
                    isCrrEnabled &&
                    isCrrStrategyAvailable(TargetType.USER_2, TrackerAppEvent.EVENT_2, trackerUrlStat, mobileAppConversions)
                ),
            TargetType.USER_3 to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA)
                .appendIf(UacStrategyName.AUTOBUDGET_CRR,
                    isCrrEnabled &&
                    isCrrStrategyAvailable(TargetType.USER_3, TrackerAppEvent.EVENT_3, trackerUrlStat, mobileAppConversions)
                ),
        )
        return mapOf(
            TargetType.INSTALL to setOf(UacStrategyName.AUTOBUDGET_AVG_CPI),
            TargetType.GOAL to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.LAUNCH to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.BASKET to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.WISH_LIST to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.REGISTER to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.TRAIN to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.ORDER to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.RATING to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.SEARCH to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.CREDITS to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.LEVELUP to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.WATCH to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.TIMESPENT to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.SHARE to setOf(UacStrategyName.AUTOBUDGET_AVG_CPA),
            TargetType.CPC to setOf(UacStrategyName.AUTOBUDGET_AVG_CLICK)
        ) + goalsWithCrr
    }

    private fun getStrategiesDataSettings(
        isOnlyCpcSkadNetwork: Boolean,
        clientId: ClientId
    ): Map<UacStrategyName, UacAvailableStrategySettings> {
        if (isOnlyCpcSkadNetwork) {
            return mapOf(
                UacStrategyName.AUTOBUDGET_AVG_CLICK to UacAvailableStrategySettings(
                    payForConversion = setOf(false)
                )
            )
        }

        val isFixCpaEnabled = featureService.isEnabledForClientId(clientId, FeatureName.UAC_FIX_CPA_STRATEGY_ENABLED)
        return mapOf(
            UacStrategyName.AUTOBUDGET_AVG_CLICK to UacAvailableStrategySettings(
                setOf(false)
            ),
            UacStrategyName.AUTOBUDGET_AVG_CPA to UacAvailableStrategySettings(
                setOf(false).appendIf(true, isFixCpaEnabled)
            ),
            UacStrategyName.AUTOBUDGET_AVG_CPI to UacAvailableStrategySettings(
                setOf(false, true)
            ),
            UacStrategyName.AUTOBUDGET_CRR to UacAvailableStrategySettings(
                setOf(false, true)
            )
        )
    }

    private fun getTrackingUrlStats(trackingUrl: TrackingUrl): UacTrackerUrlStat? {
        val plainTrackingUrl: String = removeParamsAndAnchor(trackingUrl.urlParts).toUrl()
        return uacYdbTrackerUrlStatRepository.getTrackerUrlStat(plainTrackingUrl, uacPropertiesService.crrDaysThreshold)
    }

    private fun getMobileAppStats(appInfo: AppInfo): Map<Long, MobileGoalConversions>? {
        val goalIds = TargetType.values().mapNotNull { it.goalId }
        return mobileAppConversionStatisticRepository.getConversionStats(
            appInfo.bundleId!!, appInfo.platform.name.lowercase(), goalIds, uacPropertiesService.crrDaysThreshold
        ).associateBy {
            it.goalId
        }
    }

    fun getAvailableStrategyGoalsForRmp(
        clientId: ClientId,
        trackingUrl: TrackingUrl?,
        appInfo: AppInfo?,
        isSkadNetworkEnabled: Boolean
    ): UacAvailableStrategyGoals {
        var trackingUrlStat: UacTrackerUrlStat? = null
        var mobileAppStat: Map<Long, MobileGoalConversions>? = null

        if (trackingUrl != null && appInfo != null) {
            trackingUrlStat = getTrackingUrlStats(trackingUrl)
            mobileAppStat = getMobileAppStats(appInfo)
        }

        val isIosConversionStrategiesEnabled =
            featureService.isEnabledForClientId(clientId, FeatureName.UAC_IOS_CONVERSION_STRATEGIES_ENABLED)
        val isCrrEnabled = featureService.isEnabledForClientId(clientId, FeatureName.UAC_ROAS_STRATEGY)
        return UacAvailableStrategyGoals(
            goals = getAvailableTargets(
                trackingUrlStat,
                mobileAppStat,
                !isIosConversionStrategiesEnabled && isSkadNetworkEnabled,
                isCrrEnabled
            ),
            strategiesDataSettings = getStrategiesDataSettings(isSkadNetworkEnabled, clientId)
        )
    }

    private fun getAvailableGoalsForEcomCampaign(
            operatorUid: Long,
            clientId: ClientId,
            counterIds: List<Long>,
            strategy: DbStrategy): Map<Long, Goal> {
        val goalsByType = campaignGoalsService.getAvailableGoalsForCampaignType(
                operatorUid,
                clientId,
                getCampaignTypeToCounterIds(clientId, counterIds, strategy),
                null
        )
        return goalsByType
                .values
                .flatten()
                .distinct()
                .associateBy { it.id }
    }

    private fun getCampaignTypeToCounterIds(
            clientId: ClientId,
            counterIds: List<Long>,
            strategy: DbStrategy): Set<CampaignTypeWithCounterIds> {
        val textCampaign = TextCampaign()
                .withType(CampaignType.TEXT)
                .withMetrikaCounters(counterIds)
                .withSource(CampaignSource.UAC)
                .withMetatype(CampaignMetatype.ECOM)
                .withStrategy(strategy)
        val dynamicCampaign = DynamicCampaign()
                .withType(CampaignType.DYNAMIC)
                .withMetrikaCounters(counterIds)
                .withSource(CampaignSource.UAC)
                .withMetatype(CampaignMetatype.ECOM)
                .withStrategy(strategy)
        val smartCampaign = SmartCampaign()
                .withType(CampaignType.PERFORMANCE)
                .withMetrikaCounters(counterIds)
                .withSource(CampaignSource.UAC)
                .withMetatype(CampaignMetatype.ECOM)
                .withStrategy(strategy)
        val enabledFeatures = featureService.getEnabledForClientId(clientId)
        return setOf(
                toCampaignTypeWithCounterIds(textCampaign, enabledFeatures),
                toCampaignTypeWithCounterIds(dynamicCampaign, enabledFeatures),
                toCampaignTypeWithCounterIds(smartCampaign, enabledFeatures)
        )
    }

    private fun getStrategy(strategyDataContainer: UacStrategyDataContainer) = when {
        isCpaStrategy(strategyDataContainer) -> // максимум конверсий (одна цель), оплата за конверсии
            DbStrategy()
                .withStrategyName(StrategyName.AUTOBUDGET_AVG_CPA)
                .withStrategyData(StrategyData()
                    .withGoalId(getGoalId(strategyDataContainer))
                    .withAvgCpa(strategyDataContainer.cpa)
                    .withSum(strategyDataContainer.budget)
                    .withPayForConversion(true))
        isCrrStrategy(strategyDataContainer) -> // максимум конверсий (несколько целей), оплата за конверсии
            DbStrategy()
                    .withStrategyName(StrategyName.AUTOBUDGET_CRR)
                    .withStrategyData(StrategyData()
                        .withGoalId(getGoalId(strategyDataContainer))
                        .withCrr(getCrr(strategyDataContainer))
                        .withSum(strategyDataContainer.budget)
                        .withPayForConversion(true))
        else -> DbStrategy() // максимум конверсий (несколько целей) или переходов, оплата за переходы
                .withStrategyName(StrategyName.AUTOBUDGET)
                .withStrategyData(StrategyData()
                    .withGoalId(getGoalId(strategyDataContainer))
                    .withSum(strategyDataContainer.budget))
    } as DbStrategy
}
