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

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.convertValue
import com.yandex.direct.api.v5.campaignsext.CampaignAddItem
import com.yandex.direct.api.v5.campaignsext.CampaignUpdateItem
import com.yandex.direct.api.v5.campaignsext.DailyBudgetModeEnum
import com.yandex.direct.api.v5.campaignsext.Notification
import com.yandex.direct.api.v5.campaignsext.ObjectFactory
import com.yandex.direct.api.v5.campaignsext.SmsEventsEnum
import com.yandex.direct.api.v5.campaignsext.TimeTargeting
import com.yandex.direct.api.v5.campaignsext.TimeTargetingAdd
import com.yandex.direct.api.v5.campaignsext.TimeTargetingOnPublicHolidays
import com.yandex.direct.api.v5.general.ArrayOfString
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.toBoolean
import ru.yandex.direct.api.v5.entity.campaigns.converter.toCampaignWarnPlaceInterval
import ru.yandex.direct.api.v5.entity.campaigns.converter.toLocalTime
import ru.yandex.direct.common.jackson.jaxbmodule.JaxbModule
import ru.yandex.direct.core.entity.campaign.model.CampaignWarnPlaceInterval
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.SmsFlag
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.defaultTargetingHoursCoefs
import ru.yandex.direct.libs.timetarget.WeekdayType
import java.math.BigDecimal
import java.time.LocalTime
import java.util.EnumSet

val FACTORY = ObjectFactory()

val OBJECT_MAPPER: ObjectMapper = ObjectMapper().apply {
    registerModule(JaxbModule())

    // При использовании JaxbModule null десериализуется как пустой JaxbElement
    // Поэтому поля с null при десериализации нужно явно игнорировать
    setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
}

inline fun <reified T : Any> Any.convertBean(): T =
    OBJECT_MAPPER.convertValue(this)

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
}

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 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
        )
    }

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 extractMinusKeywords(addItem: CampaignAddItem): List<String>? =
    addItem.negativeKeywords?.items

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