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

import com.yandex.direct.api.v5.campaigns.CampaignUpdateItem
import com.yandex.direct.api.v5.campaigns.PlacementType
import com.yandex.direct.api.v5.campaigns.PriorityGoalsUpdateSetting
import com.yandex.direct.api.v5.campaigns.RelevantKeywordsSetting
import com.yandex.direct.api.v5.general.ArrayOfInteger
import com.yandex.direct.api.v5.general.AttributionModelEnum
import com.yandex.direct.api.v5.general.VideoTargetEnum
import javax.xml.bind.JAXBElement
import ru.yandex.direct.api.v5.common.ConverterUtils.convertToDbPrice
import ru.yandex.direct.api.v5.common.processJaxbElement
import ru.yandex.direct.core.entity.campaign.model.BroadMatch
import ru.yandex.direct.core.entity.campaign.model.CampaignWithAttributionModel
import ru.yandex.direct.core.entity.campaign.model.CampaignWithBroadMatch
import ru.yandex.direct.core.entity.campaign.model.CampaignWithCheckPositionEvent
import ru.yandex.direct.core.entity.campaign.model.CampaignWithDayBudget
import ru.yandex.direct.core.entity.campaign.model.CampaignWithDisabledDomainsAndSsp
import ru.yandex.direct.core.entity.campaign.model.CampaignWithEshowsSettings
import ru.yandex.direct.core.entity.campaign.model.CampaignWithExcludePausedCompetingAds
import ru.yandex.direct.core.entity.campaign.model.CampaignWithAdvancedGeoTargeting
import ru.yandex.direct.core.entity.campaign.model.CampaignWithFavorite
import ru.yandex.direct.core.entity.campaign.model.CampaignWithMeaningfulGoals
import ru.yandex.direct.core.entity.campaign.model.CampaignWithMetrikaCounters
import ru.yandex.direct.core.entity.campaign.model.CampaignWithNetworkSettings
import ru.yandex.direct.core.entity.campaign.model.CampaignWithOptionalAddMetrikaTagToUrl
import ru.yandex.direct.core.entity.campaign.model.CampaignWithOptionalAddOpenstatTagToUrl
import ru.yandex.direct.core.entity.campaign.model.CampaignWithPlacementTypes
import ru.yandex.direct.core.entity.campaign.model.CampaignWithSiteMonitoring
import ru.yandex.direct.core.entity.campaign.model.CampaignWithStrategy
import ru.yandex.direct.core.entity.campaign.model.CommonCampaign
import ru.yandex.direct.core.entity.campaign.model.DbStrategy
import ru.yandex.direct.core.entity.campaign.service.validation.CampaignConstants
import ru.yandex.direct.core.entity.timetarget.model.GeoTimezone
import ru.yandex.direct.libs.timetarget.TimeTarget
import ru.yandex.direct.model.ModelChanges

fun <M : CommonCampaign> ModelChanges<M>.processCommonFields(
    item: CampaignUpdateItem,
    timeZones: Map<String, GeoTimezone>,
    oldTimeTarget: TimeTarget?,
    oldCommonCampaign: CommonCampaign?
) {
    processNotNull(item.name, CommonCampaign.NAME)
    processNotNull(item.clientInfo, CommonCampaign.FIO)
    processNotNull(item.startDate, CommonCampaign.START_DATE, ::toDate)
    processJaxbElement(item.endDate, CommonCampaign.END_DATE, ::toDate)
    processNotNull(item.timeZone, CommonCampaign.TIME_ZONE_ID) { timeZones[it]!!.timezoneId }
    processNotNull(item.timeTargeting, CommonCampaign.TIME_TARGET) {
        toTimeTarget(
            it,
            oldTimeTarget!!
        )
    }
    processJaxbElement(item.blockedIps, CommonCampaign.DISABLED_IPS) { it!!.items }
    processNotNull(extractEmail(item.notification), CommonCampaign.EMAIL)
    processNotNull(extractWarningBalance(item.notification), CommonCampaign.WARNING_BALANCE)
    processNotNull(extractSendAccountNews(item.notification), CommonCampaign.ENABLE_SEND_ACCOUNT_NEWS)
    processSmsSettings(item, oldCommonCampaign)
}

private fun <M : CommonCampaign> ModelChanges<M>.processSmsSettings(
    item: CampaignUpdateItem,
    oldCommonCampaign: CommonCampaign?
) {
    val smsSettings = item.notification?.smsSettings ?: return
    processNotNull(item, CommonCampaign.SMS_TIME) {
        extractSmsTimeInterval(it, oldCommonCampaign!!)
    }
    if (!smsSettings.events.isNullOrEmpty())
        processNotNull(smsSettings.events, CommonCampaign.SMS_FLAGS, ::extractSmsFlags)
}

fun <M : CommonCampaign> ModelChanges<M>.processCommonSettings(
    enableCompanyInfo: Boolean? = null,
    enableCpcHold: Boolean? = null,
    hasTitleSubstitution: Boolean? = null,
    isOrderPhraseLengthPrecedenceEnabled: Boolean? = null,
) {
    processNotNull(enableCompanyInfo, CommonCampaign.ENABLE_COMPANY_INFO)
    processNotNull(enableCpcHold, CommonCampaign.ENABLE_CPC_HOLD)
    processNotNull(hasTitleSubstitution, CommonCampaign.HAS_TITLE_SUBSTITUTION)
    processNotNull(isOrderPhraseLengthPrecedenceEnabled, CommonCampaign.IS_ORDER_PHRASE_LENGTH_PRECEDENCE_ENABLED)
}

fun <M : CampaignWithOptionalAddOpenstatTagToUrl> ModelChanges<M>.processHasAddOpenstatTagToUrl(
    value: Boolean?,
) = processNotNull(value, CampaignWithOptionalAddOpenstatTagToUrl.HAS_ADD_OPENSTAT_TAG_TO_URL)

fun <M : CampaignWithOptionalAddMetrikaTagToUrl> ModelChanges<M>.processHasAddMetrikaTagToUrl(
    value: Boolean?,
) = processNotNull(value, CampaignWithOptionalAddMetrikaTagToUrl.HAS_ADD_METRIKA_TAG_TO_URL)

fun <M : CampaignWithAdvancedGeoTargeting> ModelChanges<M>.processHasExtendedGeoTargeting(
    value: Boolean?,
) = processNotNull(value, CampaignWithAdvancedGeoTargeting.HAS_EXTENDED_GEO_TARGETING)

fun <M : CampaignWithAdvancedGeoTargeting> ModelChanges<M>.processUseCurrentRegion(
    value: Boolean?,
) = processNotNull(value, CampaignWithAdvancedGeoTargeting.USE_CURRENT_REGION)

fun <M : CampaignWithAdvancedGeoTargeting> ModelChanges<M>.processUseRegularRegion(
    value: Boolean?,
) = processNotNull(value, CampaignWithAdvancedGeoTargeting.USE_REGULAR_REGION)

fun <M : CampaignWithSiteMonitoring> ModelChanges<M>.processHasSiteMonitoring(
    value: Boolean?,
) = processNotNull(value, CampaignWithSiteMonitoring.HAS_SITE_MONITORING)

fun <M : CampaignWithExcludePausedCompetingAds> ModelChanges<M>.processExcludePausedCompetingAds(
    value: Boolean?,
) = processNotNull(value, CampaignWithExcludePausedCompetingAds.EXCLUDE_PAUSED_COMPETING_ADS)

fun <M : CampaignWithFavorite> ModelChanges<M>.processFavoriteForIds(
    chiefUid: Long,
    addToFavorite: Boolean?
) = processNotNull(addToFavorite, CampaignWithFavorite.FAVORITE_FOR_UIDS) {
    if (it) {
        setOf(chiefUid)
    } else {
        setOf()
    }
}

fun <M : CampaignWithAttributionModel> ModelChanges<M>.processAttributionModel(
    attributionModelEnum: AttributionModelEnum?
): ModelChanges<M> =
    processNotNull(attributionModelEnum, CampaignWithAttributionModel.ATTRIBUTION_MODEL, ::toAttributionModel)

fun <M : CampaignWithMetrikaCounters> ModelChanges<M>.processMetrikaCounters(
    counterIds: JAXBElement<ArrayOfInteger>?,
): ModelChanges<M> =
    processJaxbElement(
        counterIds,
        CampaignWithMetrikaCounters.METRIKA_COUNTERS,
        CampaignsAddRequestConverter::toMetrikaCounters,
    )

fun <M : CampaignWithDayBudget> ModelChanges<M>.processDailyBudget(
    item: CampaignUpdateItem
) {
    processJaxbElement(
        item.dailyBudget, CampaignWithDayBudget.DAY_BUDGET,
        CampaignConstants.DEFAULT_DAY_BUDGET
    ) { convertToDbPrice(it!!.amount) }
    processJaxbElement(
        item.dailyBudget, CampaignWithDayBudget.DAY_BUDGET_SHOW_MODE,
        CampaignConstants.DEFAULT_DAY_BUDGET_SHOW_MODE
    ) { toDayBudgetShowMode(it!!.mode) }
}

fun <M : CampaignWithDisabledDomainsAndSsp> ModelChanges<M>.processExcludedSites(
    item: CampaignUpdateItem,
    knownSsps: Set<String>,
) {
    processJaxbElement(
        item.excludedSites,
        CampaignWithDisabledDomainsAndSsp.DISABLED_DOMAINS,
    ) { extractDisabledDomains(it!!.items, knownSsps) }
    processJaxbElement(
        item.excludedSites,
        CampaignWithDisabledDomainsAndSsp.DISABLED_SSP,
    ) { extractDisabledSsp(it!!.items, knownSsps) }
}

fun <M : CampaignWithCheckPositionEvent> ModelChanges<M>.processCheckPositionInterval(
    item: CampaignUpdateItem
) {
    processNotNull(
        extractCheckPositionIntervalEvent(item.notification),
        CampaignWithCheckPositionEvent.CHECK_POSITION_INTERVAL_EVENT
    )
    processNotNull(
        extractEnableCheckPositionEvent(item.notification),
        CampaignWithCheckPositionEvent.ENABLE_CHECK_POSITION_EVENT
    )
}

fun <M : CampaignWithBroadMatch> ModelChanges<M>.processBroadMatch(
    relevantKeywords: JAXBElement<RelevantKeywordsSetting>?,
    oldBroadMatch: BroadMatch?
) {
    processNotNull(
        relevantKeywords,
        CampaignWithBroadMatch.BROAD_MATCH
    ) {
        BroadMatch().apply {
            broadMatchFlag = !it.isNil
            // здесь relevantKeywords переданы (хотя бы null), а значит oldBroadMatch загружен из базы и не null
            broadMatchLimit = it.value?.budgetPercent ?: oldBroadMatch!!.broadMatchLimit
            broadMatchGoalId = extractBroadMatchGoalId(it.value?.optimizeGoalId, oldBroadMatch!!)
        }
    }
}

fun <M : CampaignWithMeaningfulGoals> ModelChanges<M>.processPriorityGoals(
    priorityGoals: JAXBElement<PriorityGoalsUpdateSetting>?,
) = processJaxbElement(priorityGoals, CampaignWithMeaningfulGoals.MEANINGFUL_GOALS, ::toMeaningfulGoals)

fun <M : CampaignWithPlacementTypes> ModelChanges<M>.processPlacementTypes(
    placementTypes: List<PlacementType>?,
    oldPlacementTypes: Set<ru.yandex.direct.core.entity.campaign.model.PlacementType>?
) =
    processNotNull(placementTypes, CampaignWithPlacementTypes.PLACEMENT_TYPES) {
        toCorePlacementTypes(
            it,
            oldPlacementTypes!!
        )
    }

fun <M : CampaignWithEshowsSettings> ModelChanges<M>.processEshowSettings(
    videoTarget: VideoTargetEnum?
) =
    processNotNull(videoTarget, CampaignWithEshowsSettings.ESHOWS_SETTINGS, ::getEshowsSettings)

fun <M : CampaignWithStrategy> ModelChanges<M>.processStrategy(
    strategy: DbStrategy?
) = processNotNull(strategy, CampaignWithStrategy.STRATEGY)

fun <M : CampaignWithNetworkSettings> ModelChanges<M>.processContextLimit(
    contextLimit: Int?
) = processNotNull(contextLimit, CampaignWithNetworkSettings.CONTEXT_LIMIT)
