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

import com.yandex.direct.api.v5.campaigns.CampaignAddItem
import com.yandex.direct.api.v5.campaigns.CampaignUpdateItem
import com.yandex.direct.api.v5.campaigns.CpmBannerCampaignAddItem
import com.yandex.direct.api.v5.campaigns.DailyBudget
import com.yandex.direct.api.v5.campaigns.DailyBudgetModeEnum
import com.yandex.direct.api.v5.campaigns.DynamicTextCampaignAddItem
import com.yandex.direct.api.v5.campaigns.EmailSettings
import com.yandex.direct.api.v5.campaigns.FrequencyCapSetting
import com.yandex.direct.api.v5.campaigns.Notification
import com.yandex.direct.api.v5.campaigns.ObjectFactory
import com.yandex.direct.api.v5.campaigns.PlacementTypeArray
import com.yandex.direct.api.v5.campaigns.PlacementTypesEnum
import com.yandex.direct.api.v5.campaigns.PriorityGoalsArray
import com.yandex.direct.api.v5.campaigns.PriorityGoalsItem
import com.yandex.direct.api.v5.campaigns.PriorityGoalsUpdateSetting
import com.yandex.direct.api.v5.campaigns.RelevantKeywordsSetting
import com.yandex.direct.api.v5.campaigns.SmartCampaignAddItem
import com.yandex.direct.api.v5.campaigns.SmsEventsEnum
import com.yandex.direct.api.v5.campaigns.SmsSettings
import com.yandex.direct.api.v5.campaigns.TextCampaignAddItem
import com.yandex.direct.api.v5.campaigns.TimeTargeting
import com.yandex.direct.api.v5.campaigns.TimeTargetingAdd
import com.yandex.direct.api.v5.campaigns.TimeTargetingOnPublicHolidays
import com.yandex.direct.api.v5.general.ArrayOfString
import com.yandex.direct.api.v5.general.AttributionModelEnum
import com.yandex.direct.api.v5.general.VideoTargetEnum
import com.yandex.direct.api.v5.general.YesNoEnum
import org.joda.time.DateTimeConstants
import ru.yandex.direct.api.v5.common.ConverterUtils.convertToDbPrice
import ru.yandex.direct.api.v5.common.ConverterUtils.convertToMicros
import ru.yandex.direct.api.v5.common.toArrayOfString
import ru.yandex.direct.api.v5.common.toBoolean
import ru.yandex.direct.api.v5.common.toYesNoEnum
import ru.yandex.direct.core.entity.campaign.model.BroadMatch
import ru.yandex.direct.core.entity.campaign.model.CampaignAttributionModel
import ru.yandex.direct.core.entity.campaign.model.CampaignSource
import ru.yandex.direct.core.entity.campaign.model.CampaignWarnPlaceInterval
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.CampaignWithImpressionRate
import ru.yandex.direct.core.entity.campaign.model.CommonCampaign
import ru.yandex.direct.core.entity.campaign.model.DayBudgetShowMode
import ru.yandex.direct.core.entity.campaign.model.EshowsSettings
import ru.yandex.direct.core.entity.campaign.model.EshowsVideoType
import ru.yandex.direct.core.entity.campaign.model.MeaningfulGoal
import ru.yandex.direct.core.entity.campaign.model.PlacementType
import ru.yandex.direct.core.entity.campaign.model.RequestSource
import ru.yandex.direct.core.entity.campaign.model.SmsFlag
import ru.yandex.direct.core.entity.campaign.service.validation.CampaignConstants
import ru.yandex.direct.core.entity.time.model.TimeInterval
import ru.yandex.direct.libs.timetarget.HoursCoef
import ru.yandex.direct.libs.timetarget.TimeTarget
import ru.yandex.direct.libs.timetarget.TimeTargetUtils
import ru.yandex.direct.libs.timetarget.TimeTargetUtils.defaultTargetingHoursCoefs
import ru.yandex.direct.libs.timetarget.WeekdayType
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.time.format.ResolverStyle
import java.util.EnumSet
import javax.xml.bind.JAXBElement

val DATETIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd")
    // to throw exception on "February 30"
    // https://howtodoinjava.com/java/date-time/resolverstyle-strict-date-parsing/
    .withResolverStyle(ResolverStyle.STRICT)
val TIME_FORMATTER = DateTimeFormatter.ofPattern("H:mm")
val FACTORY = ObjectFactory()
val DEFAULT_BROAD_MATCH_GOAL_ID = 0L

fun extractTimeTarget(addItem: CampaignAddItem): TimeTarget? =
    addItem.timeTargeting?.let { toTimeTarget(it) }

fun toTimeTarget(apiTimeTarget: TimeTargetingAdd): TimeTarget =
    toTimeTarget(
        apiTimeTarget.schedule,
        apiTimeTarget.holidaysSchedule,
        null,
        apiTimeTarget.considerWorkingWeekends
    )

fun toTimeTarget(apiTimeTarget: TimeTargeting, oldTimeTarget: TimeTarget): TimeTarget =
    toTimeTarget(
        apiTimeTarget.schedule,
        apiTimeTarget.holidaysSchedule?.value,
        // если пользователь передал holidaysSchedule (даже пустой), то заполняем именно им, старое значение не нужно
        if (apiTimeTarget.holidaysSchedule != null) null else oldTimeTarget.weekdayCoefs[WeekdayType.HOLIDAY],
        apiTimeTarget.considerWorkingWeekends
    )

fun toTimeTarget(
    schedule: ArrayOfString?,
    holidaysSchedule: TimeTargetingOnPublicHolidays?,
    oldHolidaysSchedule: HoursCoef?,
    considerWorkingWeekends: YesNoEnum,
): TimeTarget {
    val result = TimeTarget()

    for (i in 1..DateTimeConstants.DAYS_PER_WEEK) {
        val day = WeekdayType.getById(i)
        result.setWeekdayCoef(day, defaultTargetingHoursCoefs())
    }

    schedule?.let {
        for (item in it.items) {
            val hoursCoef = HoursCoef()
            val oneDaySchedule = item.split(",")
            val day = WeekdayType.getById(oneDaySchedule[0].toInt())
            for (j in 0 until DateTimeConstants.HOURS_PER_DAY) {
                hoursCoef.setCoef(j, oneDaySchedule[j + 1].toInt())
            }
            result.setWeekdayCoef(day, hoursCoef)
        }
    }

    val newHolidaySchedule = holidaysSchedule?.let {
        val hoursCoef = HoursCoef()
        if (it.suspendOnHolidays == YesNoEnum.NO) {
            val bidPercent = it.bidPercent
            for (i in it.startHour until it.endHour) {
                hoursCoef.setCoef(i, bidPercent ?: TimeTarget.PredefinedCoefs.USUAL.value)
            }
        }
        hoursCoef
    } ?: oldHolidaysSchedule

    if (newHolidaySchedule != null) {
        result.setWeekdayCoef(WeekdayType.HOLIDAY, newHolidaySchedule)
    }

    if (considerWorkingWeekends == YesNoEnum.YES) {
        result.setWeekdayCoef(WeekdayType.WORKING_WEEKEND, HoursCoef())
    }

    return result
}

/**
 * Аналог функции
 * [API::Service::Campaigns::ConvertSubs::convert_time_targetings_to_external](https://a.yandex-team.ru/arc_vcs/direct/perl/api/services/v5/API/Service/Campaigns/ConvertSubs.pm?rev=r9274532#L195)
 */
fun TimeTarget?.toApiTimeTargeting(): TimeTargeting {
    val timeTarget = this
        ?.takeIf { it.weekdayCoefs.isNotEmpty() }
        ?: TimeTargetUtils.timeTarget24x7()

    return createApiTimeTargeting(timeTarget)
}

private fun createApiTimeTargeting(timeTarget: TimeTarget): TimeTargeting {
    val weekdayCoefs = timeTarget.weekdayCoefs

    return TimeTargeting().apply {
        schedule = createApiSchedule(weekdayCoefs).toArrayOfString()
        considerWorkingWeekends = (WeekdayType.WORKING_WEEKEND in weekdayCoefs).toYesNoEnum()
        holidaysSchedule = FACTORY.createTimeTargetingHolidaysSchedule(
            weekdayCoefs[WeekdayType.HOLIDAY]
                ?.let(::createTimeTargetingOnPublicHolidays)
        )
    }
}

private fun createApiSchedule(weekdayCoefs: Map<WeekdayType, HoursCoef>): List<String> {
    val schedule = mutableListOf<String>()

    for (dayNum in 1..DateTimeConstants.DAYS_PER_WEEK) {
        val rowItems = buildList {
            add(dayNum)

            val hourCoefs = weekdayCoefs[WeekdayType.getById(dayNum)] ?: HoursCoef()
            for (hour in 0 until DateTimeConstants.HOURS_PER_DAY) {
                add(hourCoefs.getCoefForHour(hour))
            }
        }

        schedule += rowItems.joinToString(",")
    }

    return schedule
}

private fun createTimeTargetingOnPublicHolidays(holidaysCoef: HoursCoef): TimeTargetingOnPublicHolidays {
    val holidaysTimeTargeting = TimeTargetingOnPublicHolidays()

    for (hour in 0 until DateTimeConstants.HOURS_PER_DAY) {
        if (holidaysCoef.getCoefForHour(hour) != TimeTarget.PredefinedCoefs.ZERO.value) {
            holidaysTimeTargeting.endHour = hour + 1
            if (holidaysCoef.getCoefForHour(hour) != TimeTarget.PredefinedCoefs.ZERO.value) {
                holidaysTimeTargeting.bidPercent = holidaysCoef.getCoefForHour(hour)
            }
        }
    }

    for (hour in DateTimeConstants.HOURS_PER_DAY - 1 downTo 0) {
        if (holidaysCoef.getCoefForHour(hour) != TimeTarget.PredefinedCoefs.ZERO.value) {
            holidaysTimeTargeting.startHour = hour
            if (holidaysTimeTargeting.bidPercent == null) {
                holidaysTimeTargeting.bidPercent = holidaysCoef.getCoefForHour(hour)
            }
        }
    }

    holidaysTimeTargeting.suspendOnHolidays =
        (holidaysTimeTargeting.bidPercent == null).toYesNoEnum()

    return holidaysTimeTargeting
}

fun toDate(date: String?): LocalDate? =
    date?.let { LocalDate.parse(it, DATETIME_FORMATTER) }

/**
 * Перловый код считает валидной датой только те, где год состоит из 4 цифр и начинается не с 0
 * https://a.yandex-team.ru/arc_vcs/direct/perl/protected/Campaign.pm?rev=r9218928#L4463
 * Вместо остальных дат возвращается undef, который в API заменяется "0000-00-00"
 *
 * В Java только дата "0000-00-00" читается из базы как null, а кампаний с `0 < year < 1000` не существует
 * То есть этот костыль с датой переносить в Java не нужно
 */
fun LocalDate.toApiDate(): String =
    format(DATETIME_FORMATTER)

fun String?.toLocalTime(): LocalTime? =
    // в перл пустая строка это "00:00"
    this?.let { LocalTime.parse(it.ifEmpty { "00:00" }, TIME_FORMATTER) }

// Возвращает дату в формате "HH:mm"
private fun createApiTime(hour: Int, minute: Int) =
    "%02d:%02d".format(hour, minute)

fun extractEmail(notification: Notification?): String? =
    notification?.emailSettings?.email

fun extractWarningBalance(notification: Notification?): Int? =
    notification?.emailSettings?.warningBalance

fun extractCheckPositionIntervalEvent(notification: Notification?): CampaignWarnPlaceInterval? =
    toCampaignWarnPlaceInterval(notification?.emailSettings?.checkPositionInterval)

fun toCampaignWarnPlaceInterval(interval: Int?): CampaignWarnPlaceInterval? =
    when (interval) {
        null -> null
        15 -> CampaignWarnPlaceInterval._15
        30 -> CampaignWarnPlaceInterval._30
        60 -> CampaignWarnPlaceInterval._60
        else -> throw IllegalArgumentException("Unknown CampaignWarnPlaceInterval")
    }

private fun CampaignWarnPlaceInterval?.toApiCheckPositionInterval(): Int? =
    when (this) {
        null -> null
        CampaignWarnPlaceInterval._15 -> 15
        CampaignWarnPlaceInterval._30 -> 30
        CampaignWarnPlaceInterval._60 -> 60
    }

fun extractSendAccountNews(addItem: CampaignAddItem): Boolean =
    // всегда проставляем sendAccountNews, потому что дефолт api5 "false" отличается от дефолта ядра "true"
    extractSendAccountNews(addItem.notification) ?: false

fun extractSendAccountNews(notification: Notification?): Boolean? =
    notification?.emailSettings?.sendAccountNews?.toBoolean()

fun extractEnableCheckPositionEvent(notification: Notification?): Boolean? =
    notification?.emailSettings?.sendWarnings?.toBoolean()

fun extractSmsTimeInterval(addItem: CampaignAddItem): TimeInterval {
    val smsSettings = addItem.notification?.smsSettings
    return extractSmsTimeInterval(smsSettings?.timeFrom.toLocalTime(), smsSettings?.timeTo.toLocalTime(),
        9, 0, 21, 0)
}

fun extractSmsTimeInterval(updItem: CampaignUpdateItem, oldCampaign: CommonCampaign): TimeInterval {
    val smsSettings = updItem.notification!!.smsSettings!!
    return extractSmsTimeInterval(
        smsSettings.timeFrom.toLocalTime(), smsSettings.timeTo.toLocalTime(),
        oldCampaign.smsTime.startHour, oldCampaign.smsTime.startMinute,
        oldCampaign.smsTime.endHour, oldCampaign.smsTime.endMinute
    )
}

private fun extractSmsTimeInterval(
    timeFrom: LocalTime?,
    timeTo: LocalTime?,
    defaultHourFrom: Int,
    defaultMinuteFrom: Int,
    defaultHourTo: Int,
    defaultMinuteTo: Int,
): TimeInterval {
    val result = TimeInterval()
    result.withStartHour(timeFrom?.hour ?: defaultHourFrom)
        .withStartMinute(timeFrom?.minute ?: defaultMinuteFrom)

    result.withEndHour(timeTo?.hour ?: defaultHourTo)
        .withEndMinute(timeTo?.minute ?: defaultMinuteTo)

    return result
}

fun extractSmsFlags(addItem: CampaignAddItem): EnumSet<SmsFlag> {
    val events = addItem.notification?.smsSettings?.events ?: listOf()
    return extractSmsFlags(events)
}

fun extractSmsFlags(events: List<SmsEventsEnum>): EnumSet<SmsFlag> =
    events
        .map { toSmsFlags(it) }
        .flatMapTo(EnumSet.noneOf(SmsFlag::class.java)) { it }

fun toSmsFlags(smsEventsEnum: SmsEventsEnum): List<SmsFlag> =
    when (smsEventsEnum) {
        SmsEventsEnum.MONITORING -> listOf(
            SmsFlag.NOTIFY_METRICA_CONTROL_SMS
        )
        SmsEventsEnum.MODERATION -> listOf(
            SmsFlag.MODERATE_RESULT_SMS
        )
        SmsEventsEnum.MONEY_IN -> listOf(
            SmsFlag.NOTIFY_ORDER_MONEY_IN_SMS
        )
        SmsEventsEnum.MONEY_OUT -> listOf(
            SmsFlag.ACTIVE_ORDERS_MONEY_OUT_SMS,
            SmsFlag.ACTIVE_ORDERS_MONEY_WARNING_SMS
        )
        SmsEventsEnum.FINISHED -> listOf(
            SmsFlag.CAMP_FINISHED_SMS
        )
    }

/**
 * Конвертация по правилам из перла:
 * https://a.yandex-team.ru/arc_vcs/direct/perl/api/services/v5/API/Service/Campaigns/ConvertSubs.pm?rev=r8833935#L57-65
 */
private fun SmsFlag.toApiSmsEventsEnum(): SmsEventsEnum? =
    when (this) {
        SmsFlag.NOTIFY_METRICA_CONTROL_SMS -> SmsEventsEnum.MONITORING
        SmsFlag.MODERATE_RESULT_SMS -> SmsEventsEnum.MODERATION
        SmsFlag.NOTIFY_ORDER_MONEY_IN_SMS -> SmsEventsEnum.MONEY_IN
        SmsFlag.ACTIVE_ORDERS_MONEY_OUT_SMS -> SmsEventsEnum.MONEY_OUT
        SmsFlag.ACTIVE_ORDERS_MONEY_WARNING_SMS -> SmsEventsEnum.MONEY_OUT
        SmsFlag.CAMP_FINISHED_SMS -> SmsEventsEnum.FINISHED
        else -> null
    }

fun <T> createApiNotificationsSettings(
    campaign: T,
): Notification where T : CommonCampaign,
                      T : CampaignWithCheckPositionEvent =
    Notification().apply {
        smsSettings = SmsSettings().apply {
            events = campaign.smsFlags
                .mapNotNull { it.toApiSmsEventsEnum() }
                .distinct()
                .ifEmpty { null }
            timeFrom = createApiTime(
                hour = campaign.smsTime.startHour,
                minute = campaign.smsTime.startMinute
            )
            timeTo = createApiTime(
                hour = campaign.smsTime.endHour,
                minute = campaign.smsTime.endMinute
            )
        }
        emailSettings = EmailSettings().apply {
            email = campaign.email
            checkPositionInterval = campaign.checkPositionIntervalEvent.toApiCheckPositionInterval()
            warningBalance = campaign.warningBalance
            sendAccountNews = campaign.enableSendAccountNews?.toYesNoEnum() ?: YesNoEnum.NO
            sendWarnings = campaign.enableCheckPositionEvent?.toYesNoEnum() ?: YesNoEnum.NO
        }
    }

fun extractDayBudget(addItem: CampaignAddItem): BigDecimal =
    addItem.dailyBudget?.amount?.let { convertToDbPrice(it) } ?: BigDecimal.ZERO

fun extractDayBudgetShowMode(addItem: CampaignAddItem): DayBudgetShowMode =
    addItem.dailyBudget?.mode?.let { toDayBudgetShowMode(it) } ?: DayBudgetShowMode.DEFAULT_

fun toDayBudgetShowMode(input: DailyBudgetModeEnum): DayBudgetShowMode =
    when (input) {
        DailyBudgetModeEnum.STANDARD -> DayBudgetShowMode.DEFAULT_
        DailyBudgetModeEnum.DISTRIBUTED -> DayBudgetShowMode.STRETCHED
    }

fun CampaignWithDayBudget.extractApiDailyBudget(): DailyBudget? =
    DailyBudget().apply {
        amount = convertToMicros(dayBudget)
        mode = dayBudgetShowMode.toApiDailyBudgetModeEnum()
    }.takeIf { it.amount != 0L || it.mode != DailyBudgetModeEnum.STANDARD }

private fun DayBudgetShowMode?.toApiDailyBudgetModeEnum(): DailyBudgetModeEnum =
    when (this) {
        null, DayBudgetShowMode.DEFAULT_ -> DailyBudgetModeEnum.STANDARD
        DayBudgetShowMode.STRETCHED -> DailyBudgetModeEnum.DISTRIBUTED
    }

fun extractMinusKeywords(addItem: CampaignAddItem): List<String>? =
    addItem.negativeKeywords?.items

fun extractDisabledIps(addItem: CampaignAddItem): List<String>? =
    addItem.blockedIps?.items

fun extractDisabledSsp(
    addItem: CampaignAddItem,
    knownSsps: Set<String>,
): List<String> = addItem.excludedSites?.items?.let { extractDisabledSsp(it, knownSsps) } ?: listOf()

fun extractDisabledSsp(
    excludedSites: List<String>,
    knownSsps: Set<String>,
): List<String> =
    knownSsps.filter { excludedSites.any { excludedSite -> excludedSite.equals(it, ignoreCase = true) } }

fun extractDisabledDomains(
    addItem: CampaignAddItem,
    knownSsps: Set<String>,
): List<String>? = addItem.excludedSites?.items?.let { extractDisabledDomains(it, knownSsps) }

fun extractDisabledDomains(
    excludedSites: List<String>,
    knownSsps: Set<String>,
): List<String> =
    excludedSites
        .toList()
        .filterNot { knownSsps.any { knownSsp -> knownSsp.equals(it, ignoreCase = true) } }
        // отбрасываем всё после пробела
        // https://a.yandex-team.ru/arc_vcs/direct/perl/protected/Direct/Validation/Domains.pm?rev=r8045921#L239
        .map { it.substringBefore(' ') }

fun CampaignWithDisabledDomainsAndSsp.extractApiExcludedSites(): ArrayOfString? {
    val excludedSites = mergeDisabledPlatforms(
        disabledDomains = disabledDomains.orEmpty(),
        disabledSsp = disabledSsp.orEmpty(),
    )
    return excludedSites
        .takeIf { it.isNotEmpty() }
        ?.toArrayOfString()
}

/**
 * Аналог функции
 * [Direct::Validation::Domains::merge_disabled_platforms](https://nda.ya.ru/t/_DnKkRJA4tzXEb)
 */
private fun mergeDisabledPlatforms(
    disabledDomains: Collection<String>,
    disabledSsp: Collection<String>,
): List<String> {
    val sspsSet = disabledSsp.toHashSet()
    return buildList {
        disabledDomains.filterTo(this) { it !in sspsSet }
        addAll(disabledSsp)
    }
}

fun extractBroadMatchLimit(addItem: TextCampaignAddItem): Int =
    addItem.relevantKeywords?.budgetPercent ?: 0

fun extractBroadMatchGoalId(addItem: TextCampaignAddItem): Long? {
    // Если параметр не задан, значение по умолчанию — 0 (а null надо задавать явно)
    val optimizeGoalId = addItem.relevantKeywords?.optimizeGoalId
    return if (optimizeGoalId != null) optimizeGoalId.value else DEFAULT_BROAD_MATCH_GOAL_ID
}

fun extractBroadMatchGoalId(
    optimizeGoalId: JAXBElement<Long>?,
    oldBroadMatch: BroadMatch,
): Long? =
// Если параметр не передан и в кампании отсутствовала структура RelevantKeywordsSetting (показы по
    // дополнительным релевантным фразам были отключены), то значение по умолчанию — 0.
    if (optimizeGoalId != null) {
        optimizeGoalId.value
    } else if (oldBroadMatch.broadMatchFlag) {
        oldBroadMatch.broadMatchGoalId
    } else {
        DEFAULT_BROAD_MATCH_GOAL_ID
    }

fun extractBroadMatchFlag(addItem: TextCampaignAddItem): Boolean =
    addItem.relevantKeywords != null

fun BroadMatch.toApiRelevantKeywordsSetting(): RelevantKeywordsSetting? =
    if (broadMatchFlag) {
        RelevantKeywordsSetting().apply {
            budgetPercent = broadMatchLimit
            optimizeGoalId = FACTORY.createRelevantKeywordsSettingOptimizeGoalId(broadMatchGoalId)
        }
    } else {
        null
    }

fun extractImpressionRateCount(addItem: CpmBannerCampaignAddItem): Int? =
    addItem.frequencyCap?.impressions

fun extractImpressionRateIntervalDays(addItem: CpmBannerCampaignAddItem): Int? =
    addItem.frequencyCap?.periodDays

fun CampaignWithImpressionRate.extractFrequencyCap(): FrequencyCapSetting? =
    FrequencyCapSetting()
        .apply {
            impressions = impressionRateCount
            periodDays = impressionRateIntervalDays
        }
        .takeIf { it.impressions != null }

fun toAttributionModel(attributionModel: AttributionModelEnum?): CampaignAttributionModel? =
    when (attributionModel) {
        null -> null
        AttributionModelEnum.LC -> CampaignAttributionModel.LAST_CLICK
        AttributionModelEnum.LSC -> CampaignAttributionModel.LAST_SIGNIFICANT_CLICK
        AttributionModelEnum.FC -> CampaignAttributionModel.FIRST_CLICK
        AttributionModelEnum.LYDC -> CampaignAttributionModel.LAST_YANDEX_DIRECT_CLICK
        AttributionModelEnum.LSCCD -> CampaignAttributionModel.LAST_SIGNIFICANT_CLICK_CROSS_DEVICE
        AttributionModelEnum.FCCD -> CampaignAttributionModel.FIRST_CLICK_CROSS_DEVICE
        AttributionModelEnum.LYDCCD -> CampaignAttributionModel.LAST_YANDEX_DIRECT_CLICK_CROSS_DEVICE
    }

fun CampaignAttributionModel?.toApiAttributionModel(): AttributionModelEnum =
    when (this) {
        CampaignAttributionModel.LAST_CLICK -> AttributionModelEnum.LC
        CampaignAttributionModel.LAST_SIGNIFICANT_CLICK -> AttributionModelEnum.LSC
        CampaignAttributionModel.FIRST_CLICK -> AttributionModelEnum.FC
        CampaignAttributionModel.LAST_YANDEX_DIRECT_CLICK -> AttributionModelEnum.LYDC
        CampaignAttributionModel.LAST_SIGNIFICANT_CLICK_CROSS_DEVICE -> AttributionModelEnum.LSCCD
        CampaignAttributionModel.FIRST_CLICK_CROSS_DEVICE -> AttributionModelEnum.FCCD
        CampaignAttributionModel.LAST_YANDEX_DIRECT_CLICK_CROSS_DEVICE -> AttributionModelEnum.LYDCCD
        // если в БД attribution model - null, возвращается дефолтное значение
        null -> CampaignConstants.DEFAULT_ATTRIBUTION_MODEL.toApiAttributionModel()
    }

fun extractMeaningfulGoals(campaignItem: SmartCampaignAddItem): List<MeaningfulGoal>? =
    campaignItem.priorityGoals?.let { toMeaningfulGoals(it) }

fun extractMeaningfulGoals(campaignItem: TextCampaignAddItem): List<MeaningfulGoal>? =
    campaignItem.priorityGoals?.let { toMeaningfulGoals(it) }

fun extractMeaningfulGoals(campaignItem: DynamicTextCampaignAddItem): List<MeaningfulGoal>? =
    campaignItem.priorityGoals?.let { toMeaningfulGoals(it) }

private fun toMeaningfulGoals(priorityGoals: PriorityGoalsArray): List<MeaningfulGoal> =
    priorityGoals.items.map {
        MeaningfulGoal()
            .withConversionValue(convertToDbPrice(it.value))
            .withGoalId(it.goalId)
            .withIsMetrikaSourceOfValue(it.isMetrikaSourceOfValue?.toBoolean())
    }

fun toMeaningfulGoals(priorityGoals: PriorityGoalsUpdateSetting?): List<MeaningfulGoal>? =
    priorityGoals?.items?.map {
        MeaningfulGoal().apply {
            conversionValue = convertToDbPrice(it.value)
            goalId = it.goalId
            isMetrikaSourceOfValue = it.isMetrikaSourceOfValue?.toBoolean()
        }
    }

fun List<MeaningfulGoal>?.toApiPriorityGoalsArray(): PriorityGoalsArray? =
    orEmpty()
        .map(::createApiPriorityGoal)
        .ifEmpty { null }
        ?.let { PriorityGoalsArray().withItems(it) }

private fun createApiPriorityGoal(goal: MeaningfulGoal): PriorityGoalsItem =
    PriorityGoalsItem().apply {
        goalId = goal.goalId
        value = convertToMicros(goal.conversionValue)
        isMetrikaSourceOfValue = goal.isMetrikaSourceOfValue?.toYesNoEnum() ?: YesNoEnum.NO
    }

fun getEnableCpcHold(isAutobudgetStrategy: Boolean): Boolean =
    // иногда в API флаг MAINTAIN_NETWORK_CPC не принимаем и тогда его учитывать не нужно
    !isAutobudgetStrategy

fun getEnableCpcHold(isAutobudgetStrategy: Boolean, maintainNetworkCpc: Boolean): Boolean =
// https://a.yandex-team.ru/arc_vcs/direct/perl/api/services/v5/API/Service/Campaigns.pm?rev=r8456865#L665
// https://a.yandex-team.ru/arc_vcs/direct/perl/protected/Campaign.pm?rev=r8605372#L536
    // https://a.yandex-team.ru/arc_vcs/direct/perl/protected/Campaign.pm?rev=r8605372#L2111-2115
    !isAutobudgetStrategy && maintainNetworkCpc

fun toCampaignSource(requestSource: RequestSource?): CampaignSource =
    when (requestSource) {
        RequestSource.API_USLUGI -> CampaignSource.USLUGI
        RequestSource.API_GEO -> CampaignSource.GEO
        RequestSource.API_EDA -> CampaignSource.EDA
        RequestSource.API_DC -> CampaignSource.DC
        else -> CampaignSource.API
    }

fun extractPlacementTypes(campaignItem: DynamicTextCampaignAddItem): Set<PlacementType> =
    toCorePlacementTypes(campaignItem.placementTypes)

/**
 * В add неуказанные категории устанавливаются в YES
 */
fun toCorePlacementTypes(
    wsdlPlacementTypes: List<com.yandex.direct.api.v5.campaigns.PlacementType>,
): Set<PlacementType> =
// пустое множество - значение по умолчанию - все, в базе хранится как пустое множество, чтобы при добавлении
    // нового значения в enum в существующии кампании это значение прорастало автоматически
    if (wsdlPlacementTypes.isEmpty()) emptySet()
    else PlacementType.values()
        .filter { core -> wsdlPlacementTypes.none { (toCoreType(it.type) == core) && it.value == YesNoEnum.NO } }
        .toSet()

fun toCorePlacementTypes(
    wsdlPlacementTypes: List<com.yandex.direct.api.v5.campaigns.PlacementType>,
    oldPlacementTypes: Set<PlacementType>,
): Set<PlacementType> {
    val currentPlacementTypes =
        if (oldPlacementTypes.isNotEmpty()) oldPlacementTypes
        else PlacementType.values().toSet()
    val toAdd = wsdlPlacementTypes.filter { it.value == YesNoEnum.YES }
        .map { (toCoreType(it.type)) }
    val toDelete = wsdlPlacementTypes.filter { it.value == YesNoEnum.NO }
        .map { (toCoreType(it.type)) }
    return currentPlacementTypes + toAdd - toDelete
}

fun toCoreType(type: PlacementTypesEnum): PlacementType =
    when (type) {
        PlacementTypesEnum.SEARCH_RESULTS -> PlacementType.SEARCH_PAGE
        PlacementTypesEnum.PRODUCT_GALLERY -> PlacementType.ADV_GALLERY
    }

fun Collection<PlacementType>?.toApiPlacementTypesArray(): PlacementTypeArray {
    val enabledTypes = this.orEmpty()
        .ifEmpty { listOf(PlacementType.SEARCH_PAGE, PlacementType.ADV_GALLERY) }
        .map { it.toApiPlacementTypesEnum() }

    return PlacementTypesEnum.values()
        .map { placementTypeEnum ->
            com.yandex.direct.api.v5.campaigns.PlacementType().apply {
                type = placementTypeEnum
                value = (placementTypeEnum in enabledTypes).toYesNoEnum()
            }
        }
        .let { PlacementTypeArray().withItems(it) }
}

fun PlacementType.toApiPlacementTypesEnum(): PlacementTypesEnum =
    when (this) {
        PlacementType.SEARCH_PAGE -> PlacementTypesEnum.SEARCH_RESULTS
        PlacementType.ADV_GALLERY -> PlacementTypesEnum.PRODUCT_GALLERY
    }

fun getEshowsSettings(videoTarget: VideoTargetEnum?): EshowsSettings? {
    if (videoTarget == null) return null
    return EshowsSettings()
        .withVideoType(toVideoType(videoTarget))
}

fun toVideoType(videoTarget: VideoTargetEnum?) =
    when (videoTarget) {
        VideoTargetEnum.VIEWS -> EshowsVideoType.COMPLETES
        VideoTargetEnum.CLICKS -> EshowsVideoType.LONG_CLICKS
        null -> null
    }

fun CampaignWithEshowsSettings.extractVideoTarget(): VideoTargetEnum? =
    when (eshowsSettings.videoType) {
        EshowsVideoType.COMPLETES -> VideoTargetEnum.VIEWS
        EshowsVideoType.LONG_CLICKS -> VideoTargetEnum.CLICKS
        null -> null
    }
