import com.google.common.base.Joiner
import com.gridnine.xtrip.common.hotels2.model.FindReservationsParameters
import com.gridnine.xtrip.common.hotels2.model.HotelSearchDateType
import com.gridnine.xtrip.common.hotels2.model.HotelsAggregatorGdsAccount
import com.gridnine.xtrip.common.hotels2.model.ReservationInfo
import com.gridnine.xtrip.common.midoffice.helper.ReservationGdsNameInfoHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.Xeption
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.booking.Reservation
import com.gridnine.xtrip.common.model.booking.ReservationStatus
import com.gridnine.xtrip.common.model.booking.ReservationType
import com.gridnine.xtrip.common.model.booking.GeneralProductCommission
import com.gridnine.xtrip.common.model.booking.xtriphotels.*
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.PreferenceKey
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.entity.EntityStorageContext
import com.gridnine.xtrip.common.model.entity.EntityStorageHelper
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.helpers.DictHelper
import com.gridnine.xtrip.common.model.helpers.GeneralProductHelper
import com.gridnine.xtrip.common.model.helpers.HotelProductHelper
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil
import com.gridnine.xtrip.server.hotels2.ibus.IBusHotelsContextKeys
import com.gridnine.xtrip.server.ibus.IntegrationBusFacade
import groovy.transform.CompileStatic
import groovy.transform.Field

import java.time.LocalDate
import java.time.temporal.ChronoUnit

createStyle(name: 'title', fontBold: true, h_span: 7, v_alignment: 'CENTER', fontHeight: 16)
createStyle(name: 'header', fontBold: false, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 10, leftBorder: 'MEDIUM', rightBorder: 'MEDIUM', topBorder: 'MEDIUM', bottomBorder: 'MEDIUM', wrapText: true)
createStyle(name: 'data', fontBold: false, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 10, leftBorder: 'THIN', rightBorder: 'THIN', topBorder: 'THIN', bottomBorder: 'THIN')
createStyle(name: 'textData', parent: 'data', h_alignment: 'CENTER')
createStyle(name: 'numberData', parent: 'data', h_alignment: 'CENTER')
createStyle(name: 'error', foreground: 'YELLOW', h_alignment: 'CENTER')
createStyle(name: 'errorBig', foreground: 'RED', h_alignment: 'CENTER')
createStyle(name: 'errorMessage', h_span: 7, v_alignment: 'CENTER', h_alignment: 'LEFT', fontHeight: 8)

@Field final titleRowHeight = 40
@Field final headerRowHeight = 40
@Field final dataRowHeight = 12

@Field final Date startDate = parameters['key-report-params']['periodBegin'] as Date
@Field final Date endDate = parameters['key-report-params']['periodEnd'] as Date
@Field final HotelSearchDateType hotelSearchParameter = parameters['filterByDate'] as HotelSearchDateType
@Field final EntityReference<Organization> organizationReference = parameters['agency'] as EntityReference
@Field final EntityReference<HotelsAggregatorGdsAccount> gdsAccount = parameters['GDS_ACCOUNT'] as EntityReference<HotelsAggregatorGdsAccount>
@Field final boolean includeNonApiOrders = parameters['includeNonApiOrders']

@Field Map<String, List<MainHotelProductIndex>> ticketMap
@Field final EntityStorageContext entityStorageContext = EntityStorageHelper.createContext()
@Field final String equivCurrCode = DictHelper.getPreferenceValue(PreferenceKey.EQUIVE_CURRENCY, null)
@Field final HotelsAggregatorGdsAccount gdsAccountEntity = EntityStorage.get().resolve(gdsAccount)?.entity
@Field final HotelProvider provider = gdsAccountEntity?.provider
@Field final int MAX_INTERVAL = 2 // interval = 1 day

@Field final String dataNotInProvider = "Поставщик не прислал данные"
@Field final String dataNotInMOM = "Данные отсутствуют в базе МОМ"
@Field final String orderNotFoundInMom = "Заказ не найден в МОМ"
@Field final String orderNotFoundInProvider = "Заказ не найден у поставщика"

validateParameters()
generate() //ENTRY POINT

void generate() {
    List<ReservationInfo> reservations =
            getReservationsFromAggregator()
    renderTable()

    //Составление мапы заказов, которые пришли из МОМ
    ticketMap = [:].withDefault { [] }
    tickets { MainHotelProductIndex prod ->
        if (HotelProvider.OSTROVOK == provider && !includeNonApiOrders) {
            Reservation res = getProduct(prod)?.reservation
            if (res != null && ReservationGdsNameInfoHelper.getOnlineGdsAccount(res) == gdsAccount) {
                if (TextUtil.nonBlank(prod.displayedRecordLocator)) {
                    ticketMap[prod.displayedRecordLocator].add(prod)
                } else {
                    ticketMap[prod.recordLocator].add(prod)
                }
            }
        } else {
            if (TextUtil.nonBlank(prod.displayedRecordLocator)) {
                ticketMap[prod.displayedRecordLocator].add(prod)
            } else {
                ticketMap[prod.recordLocator].add(prod)
            }
        }
    }

    //Составление мапы заказов от агрегатора, где ключ номер заказа
    Map<String, List<ReservationInfo>> resByPnr = [:].withDefault { [] }
    reservations.each { ReservationInfo it ->
        if (it.recordLocator) {
            resByPnr[it.recordLocator].add(it)
        } else {
            resByPnr[it.recordLocatorProvider].add(it)
        }
    }

    //создаем список заказов которых нет в MOM
    List<ReservationInfo> resNotInMom = reservations
            .grep {
                if (it.recordLocator) {
                    !ticketMap.containsKey(it.recordLocator)
                } else {
                    !ticketMap.containsKey(it.recordLocatorProvider)
//если заказы от поставщика не находятся ни по recordLocator, ни по recordLocatorProvider по данным из базы
                }
            }.sort(false) {
        if (it.recordLocator) {
            it.recordLocator
        } else {
            it.recordLocatorProvider
        }
    }.reverse()

    //создание мапы заказов которые есть в МОМе и агрегаторе
    Map<String, List<MainHotelProductIndex>> ticketsInMomAndProvider =
            ticketMap.findAll { resByPnr.containsKey(it.key) }

    //создание мапы заказов которые есть в МОМе, но нет в агрегаторе
    Map<String, List<MainHotelProductIndex>> ticketsNotInProvider =
            ticketMap.findAll { !resByPnr.containsKey(it.key) }

    //ЗАКАЗЫ И У ПОСТАВЩИКА И В МОМ
    ticketsInMomAndProvider.entrySet().sort(false) { it.key }.reverse().each { entry ->
        def pnr = entry.key
        def resInfos = resByPnr[pnr]
        def ticketsInBooking = entry.value

        if (hasCallCenter(ticketsInBooking)) {
            return
        }
        List<ProviderClass> providerData = getDataFromProvider(resInfos)
        List<TicketData> momTicketData = getTicketData(ticketsInBooking)
        equalsOrders(momTicketData, providerData)
    }

    //ЗАКАЗЫ ТОЛЬКО У ПОСТАВЩИКА
    resNotInMom.each { resInfo ->
        List<ProviderClass> providerData = getDataFromProvider(Collections.singletonList(resInfo))
        //Данные в МОМ
        List<TicketData> momTicketData = getTicketData(getOrdersFromMOM(resInfo))
        equalsOrders(momTicketData, providerData)
    }

    //ЗАКАЗЫ ТОЛЬКО В МОМ
    ticketsNotInProvider.entrySet().sort(false) { it.key }.reverse().each { entry ->
        List<MainHotelProductIndex> ticketsInBooking = entry.value
        if (hasCallCenter(ticketsInBooking)) {
            return
        }
        List<TicketData> ticketData = getTicketData(ticketsInBooking)
        equalsOrders(ticketData, Collections.emptyList())
    }
}

@CompileStatic
LocalDate getLastPeriodDate(LocalDate beginDate, LocalDate endDate) {
    if (ChronoUnit.DAYS.between(beginDate, endDate) <= MAX_INTERVAL) {
        return endDate
    } else {
        return beginDate.plusDays(MAX_INTERVAL)
    }
}

void renderTable() {
//-----------------------------------Блок формирования таблицы Excel----------------------------------------------------
    nextRow()
    rowHeight(titleRowHeight)
    StringBuilder supplierTitle = new StringBuilder('Поставщик: ')
    supplierTitle.append(provider.toString())
    text(supplierTitle.toString(), 'title')

//печать названия агенства
    nextRow()
    rowHeight(titleRowHeight)
    text('Агентство: ' + organizationReference, 'title')

    nextRow()
    rowHeight(headerRowHeight)
//1
    text('Номер заказа МОМ', 'header')
    columnWidth(22)
//2
    nextColumn()
    text('Номер заказа у Поставщика', 'header')
    columnWidth(22)
//3
    nextColumn()
    text('Статус заказа Агрегатора', 'header')
    columnWidth(30)
//4
    nextColumn()
    text('Статус заказа МОМ', 'header')
    columnWidth(30)
//5
    nextColumn()
    text('Дата создания бронирования в МОМ', 'header')
    columnWidth(30)
//6
    nextColumn()
    text('Дата создания бронирования у Поставщика', 'header')
    columnWidth(30)
//7
    nextColumn()
    text('Дата заезда у Поставщика', 'header')
    columnWidth(30)
//8
    nextColumn()
    text('Наименование Агента у Поставщика', 'header')
    columnWidth(30)
//9
    nextColumn()
    text('Фамилии путешественников у Поставщика', 'header')
    columnWidth(30)
//10
    nextColumn()
    text('Примененные штрафы в МОМ', 'header')
    columnWidth(30)
//11
    nextColumn()
    text('Примененные штрафы поставщика', 'header')
    columnWidth(30)
//12
    nextColumn()
    text('Сумма заказа в МОМ без учета добора и сборов', 'header')
    columnWidth(30)
//13
    nextColumn()
    text('Валюта заказа в МОМ', 'header')
    columnWidth(15)
//14
    nextColumn()
    text('Сумма у Поставщика', 'header')
    columnWidth(15)
//15
    nextColumn()
    text('Валюта заказа у Поставщика', 'header')
    columnWidth(15)
//15
    nextColumn()
    text('Сумма НДС в МОМ', 'header')
    columnWidth(15)
//16
    nextColumn()
    text('Сумма НДС у Поставщика', 'header')
    columnWidth(15)
//17
    nextColumn()
    text('Сумма комиссии Агентства в МОМ', 'header')
    columnWidth(20)
//18
    nextColumn()
    text('Сумма комиссии у Поставщика', 'header')
    columnWidth(20)
//19
    nextColumn()
    text('Примечание', 'header')
    columnWidth(120)
//----------------------------------------------------------------------------------------------------------------------
}

static MoneyWrapper getMomBaseSum(HotelProduct product) {
    MoneyWrapper result = new MoneyWrapper()
    if (product == null) {
        return result
    }
    ProductStatus status = product.getStatus()
    String equivalentCurrency = product.getGdsCurrency()
    def (BigDecimal equivalentPrice, String baseCurrency, BigDecimal basePrice) = getPrices(product)
    BigDecimal sumBasAddSer = getAdditionalServicesCost(product.getAdditionalServices()) //  в equivalentCurrency
    (basePrice, equivalentPrice) = addServices(sumBasAddSer, equivalentCurrency, equivalentPrice, baseCurrency, basePrice)
    result.basePrice = negate(basePrice, status)
    result.basePriceCurrency = baseCurrency
    result.equivalentPrice = negate(equivalentPrice, status)
    result.equivalentPriceCurrency = equivalentCurrency
    BigDecimal equivVatPrice = product.rooms.first().equivalentVatPrice
    if (equivVatPrice != null) {
        result.equivalentVatPrice = negate(equivVatPrice, status)
    }
    result.commission = negate(getHotelCommissionAmount(product), status)
    return result
}

@CompileStatic
protected static List getPrices(HotelProduct product) {
    BigDecimal basePrice = BigDecimal.ZERO
    BigDecimal equivalentPrice = BigDecimal.ZERO
    String baseCurrency = null
    for (Room room : product.getRooms()) {
        if (room.getBasePrice() != null && room.getBasePrice().getValue() != null) {
            basePrice = basePrice.add(room.getBasePrice().getValue())
            baseCurrency = room.getBasePrice().getCurrency()
        }
        if (room.getEquivalentPrice() != null) {
            equivalentPrice = equivalentPrice.add(room.getEquivalentPrice())
        }
    }
    [equivalentPrice, baseCurrency, basePrice]
}

/**
 * IBECORP-2747
 */
@CompileStatic
protected static List getVendorFopsPrice(HotelProduct product) {
    def contractRelation =
            GeneralProductHelper.getVendorContractRelation(product)
    def currencyCode =
            DictHelper.getCode(GeneralProductHelper.getCurrency(contractRelation))
    BigDecimal fopsEquivalentAmount = contractRelation.getFops().stream()
            .map({ fop -> fop.getEquivalentAmount() })
            .reduce({ BigDecimal a1, BigDecimal a2 -> a1.add(a2) }).orElse(BigDecimal.ZERO)

    [fopsEquivalentAmount, currencyCode]
}

@CompileStatic
protected static List addServices(BigDecimal sumBasAddSer, String equivalentCurrency,
                                  BigDecimal equivalentPrice, String baseCurrency, BigDecimal basePrice) {
    if (sumBasAddSer.signum() != 0) {
        if (equivalentCurrency == baseCurrency) {
            basePrice = basePrice.add(sumBasAddSer)
        }
        equivalentPrice = equivalentPrice.add(sumBasAddSer)
    }
    [basePrice, equivalentPrice]
}

/**
 *  VIP-40536
 *  М. Королёва:
 *      Нужно доработать, чтобы к стоимости прибавлялись только те услуги, у которых в колонке цена не пустое значение.
 *      Если значение пустое - стоимость доп услуги уже включена в стоимость проживания и суммироваться с ней не должна.
 *      -----------
 *      В колонке цена находится equivalentAmount. Скорее всего, валюта у него рубль.
 *
 */
@CompileStatic
static BigDecimal getAdditionalServicesCost(List<AdditionalService> additionalServices) {
    if (additionalServices == null || additionalServices.isEmpty()) {
        return BigDecimal.ZERO
    }
    BigDecimal res = BigDecimal.ZERO
    for (AdditionalService addService : additionalServices) {
        BigDecimal amount = getNonNull(addService.getEquivalentAmount())
        res = res.add(amount)
    }
    return res
}

@CompileStatic
static BigDecimal getHotelCommissionAmount(HotelProduct product) {
    Collection<GeneralProductCommission> vendorCommissions =
            GeneralProductHelper.filterCommissions(
                    HotelProductHelper.getUnmodifiableCommissions(product, ContractType.VENDOR),
                    GeneralProductHelper.commissionCommissionPropertyTypes,
                    null, null)
    return vendorCommissions.collect { commission -> commission.equivalentAmount }
            .inject(BigDecimal.ZERO, { amount1, amount2 -> MiscUtil.sum(amount1, amount2) })
}

@CompileStatic
static BigDecimal getNonNull(BigDecimal value) {
    return (value != null) ? value : BigDecimal.ZERO
}

List<TicketData> getTicketData(List<MainHotelProductIndex> ticketList) {
    List<TicketData> resultList = new ArrayList<>()

    ticketList.each {
        BookingFile bookingFile = getBookingCnt(it)?.entity
        HotelProduct product = (HotelProduct) BookingHelper.findProductByUid(
                it.navigationKey, bookingFile)
        TicketData result = new TicketData()
        setData(result, it, product?.reservation?.status)
        //Устанавливаем имена путешественников
        result.ticketListName = it.travellersNames
        result.setMoney(getMoneyData(product))
        if (product == null || (product != null
                && product.status != ProductStatus.REFUND)) {
            resultList.add(result)
        }
    }

    return resultList
}

@CompileStatic
class ProductMoneyData {
    String baseSumCurrency
    String equivalentSumCurrency
    String currencyVendorFops
    BigDecimal baseSum = BigDecimal.ZERO
    BigDecimal equivalentSum = BigDecimal.ZERO
    BigDecimal equivalentVatSum
    BigDecimal commissionSum = BigDecimal.ZERO
    BigDecimal penalty = BigDecimal.ZERO
    BigDecimal vendorFopsSum = BigDecimal.ZERO

    ProductMoneyData plus(ProductMoneyData other) {
        return new ProductMoneyData(
                baseSum: this.baseSum.add(other.baseSum),
                equivalentSum: this.equivalentSum.add(other.equivalentSum),
                equivalentVatSum: this.equivalentVatSum?.add(other?.equivalentVatSum),
                commissionSum: this.commissionSum.add(other.commissionSum),
                penalty: this.penalty.add(other.penalty),
                vendorFopsSum: this.vendorFopsSum.add(other.vendorFopsSum),
                baseSumCurrency: merge(this.baseSumCurrency, other.baseSumCurrency),
                equivalentSumCurrency: merge(this.equivalentSumCurrency, other.equivalentSumCurrency),
                currencyVendorFops: merge(this.currencyVendorFops, other.currencyVendorFops)
        )
    }

    static String merge(String one, String other) {
        return one ?: other
    }
}

@CompileStatic
static ProductMoneyData getData(HotelProduct product) {
    ProductMoneyData result = new ProductMoneyData()
    if (!product) {
        return result
    }
    MoneyWrapper moneyWrapper = getMomBaseSum(product)
    result.baseSumCurrency = moneyWrapper.basePriceCurrency
    result.equivalentSumCurrency = moneyWrapper.equivalentPriceCurrency
    result.penalty = getPenalty(product)
    if ("RUB" != moneyWrapper.equivalentPriceCurrency) {
        def vendorFopsPrice = getVendorFopsPrice(product)
        result.vendorFopsSum = vendorFopsPrice[0] as BigDecimal
        result.currencyVendorFops = vendorFopsPrice[1] as String
    }
    result.baseSum = moneyWrapper.basePrice
    result.equivalentSum = moneyWrapper.equivalentPrice
    result.equivalentVatSum = moneyWrapper.equivalentVatPrice
    result.commissionSum = moneyWrapper.commission
    return result
}


@CompileStatic
private static BigDecimal getPenalty(HotelProduct hp) {
    BigDecimal penaltiesInMOM = BigDecimal.ZERO
    if (hp != null && hp.penalties != null) {
        for (Penalty penalty : hp.penalties) {
            if (penalty && penalty.equivalentAmount) {
                penaltiesInMOM = penaltiesInMOM.add(penalty.equivalentAmount)
            }
        }
    }
    return penaltiesInMOM
}


@CompileStatic
static void setData(TicketData result, MainHotelProductIndex productIndex, ReservationStatus status) {
    result.status = status
    result.bookingNumberInMOM = productIndex.bookingNumber
    result.checkInDate = productIndex.hotelCheckInDate
    result.checkOutDate = productIndex.hotelCheckOutDate
    result.agentInMOM = productIndex.agent
    result.creationReservationDateInMOM = productIndex.reservationCreationDate
    result.travellersName = String.join(", ", productIndex.travellersNames)
}

@CompileStatic
static ProductMoneyData getMoneyData(HotelProduct product) {
    ProductMoneyData result = new ProductMoneyData()
    if (product != null) {
        result = getData(product)
        HotelProduct nextProduct = product.getNextProduct()
        if (nextProduct != null && nextProduct.status == ProductStatus.REFUND) {
            result += getData(nextProduct)
        }
    }
    return result
}
//----------------------------------------------------------------------------------------------------------------------
@CompileStatic
static boolean isEqual(BigDecimal a, BigDecimal b) {
    if (a == null || b == null) {
        return false
    }
    return a == b
}

@CompileStatic
static boolean hasCallCenter(List<MainHotelProductIndex> tickets) {
    null != tickets.find {
        it.reservationType == ReservationType.CALL_CENTER || it.reservationType == ReservationType.OFFLINE
    }
}

@CompileStatic
static boolean isEquals(Date date1, Date date2) {
    if (date1 == null || date2 == null) {
        return false
    }
    return MiscUtil.clearTime(date1) == MiscUtil.clearTime(date2)
}

@CompileStatic
EntityContainer<BookingFile> getBookingCnt(MainHotelProductIndex productIndex) {
    EntityContainer<BookingFile> res =
            EntityStorageHelper.resolve(productIndex.source, entityStorageContext)
    if (EntityStorageHelper.isEmpty(res)) {
        warn "Не удалось зарезолвить index=${productIndex.source}"
        return null
    }
    return res
}

@CompileStatic
HotelProduct getProduct(MainHotelProductIndex productIndex) {
    EntityContainer entityContainer = getBookingCnt(productIndex)
    if (entityContainer != null) {
        return (HotelProduct) BookingHelper.findProductByUid(productIndex.navigationKey, entityContainer.entity)
    }
    return null
}

@CompileStatic
static String getDate(LocalDate date) {
    if (date == null) {
        return null
    }
    return String.format("%tD", date)
}

@CompileStatic
static List<ProviderClass> getDataFromProvider(List<ReservationInfo> reservationInfos) {
    List<ProviderClass> resultList = new ArrayList<>()

    reservationInfos.each {
        ProviderClass result = new ProviderClass()

        result.providerOrderNumber = it.recordLocatorProvider
        result.statusInHotelAggregator = it.getStatus()?.name()
        result.commissionInProvider = it.hotelCommission?.amount?.value
        result.sumInProvider = it.getBasePrice()?.getValue()
        result.currencyInProvider = it.getBasePrice()?.getCurrency()
        result.vatSumInProvider = it.vatAmount
        result.penaltyInProvider = it.penaltyAmount?.value ?: BigDecimal.ZERO
        result.creationOrderDateInProvider = getDate(it.registrationDate)
        result.agentInProvider = it.contactPerson
        result.checkInDateInProvider = getDate(it.checkInDate)
        result.checkOutDateInProvider = getDate(it.checkOutDate)
        result.checkInDateForEqual = MiscUtil.toDate(it.checkInDate)
        result.checkOutDateForEqual = MiscUtil.toDate(it.checkOutDate)
        result.travellersNameInProvider = it.travellers
                .collect { it.name ? it.name : it.nameInGds }
                .join(", ")

        resultList.add(result)
    }

    return resultList
}

void equalsOrders(List<TicketData> momTicketDataList, List<ProviderClass> providerDataList) {
    if (momTicketDataList.size() <= 1 && providerDataList.size() <= 1) {
        TicketData momTicketData = momTicketDataList.size() == 1 ? momTicketDataList.first() : new TicketData()
        ProviderClass providerData = providerDataList.size() == 1 ? providerDataList.first() : new ProviderClass()
        fillReport(momTicketData, providerData)
    } else {
        def momTicketDataIterator = momTicketDataList.iterator()
        while (momTicketDataIterator.hasNext()) {
            TicketData momTicketData = momTicketDataIterator.next()
            def providerIterator = providerDataList.iterator()
            while (providerIterator.hasNext()) {
                ProviderClass providerData = providerIterator.next()
                if (momTicketData.travellersName == providerData.travellersNameInProvider) {
                    fillReport(momTicketData, providerData)
                    momTicketDataIterator.remove()
                    providerIterator.remove()
                    break
                }
            }
        }
        momTicketDataList.each { fillReport(it, new ProviderClass()) }
        providerDataList.each { fillReport(new TicketData(), it) }
    }
}

void fillReport(TicketData momTicketData, ProviderClass providerData) {
    //Данные поставщика
    String providerOrderNumber = providerData.providerOrderNumber
    BigDecimal providerSum = providerData.sumInProvider
    String providerCurrency = providerData.currencyInProvider
    BigDecimal providerVatSum = providerData.vatSumInProvider
    BigDecimal commissionSum = providerData.commissionInProvider
    String createOrderDateInProvider = providerData.creationOrderDateInProvider
    String agentInProvider = providerData.agentInProvider
    String checkInDateInProvider = providerData.checkInDateInProvider
    String statusInHotelAggregator = providerData.statusInHotelAggregator
    BigDecimal penaltyInProvider = providerData.penaltyInProvider
    String travellersNameInProvider = providerData.travellersNameInProvider

    //Данные из базы МОМа
    String bookingNumberInMOM = momTicketData.bookingNumberInMOM
    BigDecimal momSum = momTicketData.baseSum
    BigDecimal momVatSum = momTicketData.equivalentVatSum
    String momCurrency = momTicketData.baseSumCurrency
    BigDecimal momEquivSum = momTicketData.equivalentSum
    String momEquivCurrency = momTicketData.equivalentSumCurrency
    if (momEquivCurrency == null) {
        momEquivCurrency = equivCurrCode
    }

    BigDecimal momVendorFopsSum = momTicketData.vendorFopsSum
    String momVendorFopsSumCurrency = momTicketData.currencyVendorFops

    String dateCreationReservationInMOM = getDate(MiscUtil.toLocalDate(momTicketData.creationReservationDateInMOM))

    if (providerSum && momSum && provider == HotelProvider.RCR) {
        providerSum = providerSum.setScale(2, BigDecimal.ROUND_HALF_DOWN)
        momSum = momSum.setScale(2, BigDecimal.ROUND_HALF_DOWN)
    }

    boolean isCurTSumNotT = false
    String isCurTSumNotTString = "Валюта суммы поставщика не рубли, требуется проверить суммы в валюте"

    boolean isAllCurNotT = false
    String isAllCurNotTString = "Валюта суммы заказа в МОМ отличается от валюты суммы поставщика, требуется проверить суммы в валюте"

    if (providerCurrency && momCurrency && Objects.equals(providerCurrency, momCurrency)) {
        if (!Objects.equals(providerCurrency, "RUB")) {
            warn 'providerCurrency: ' + providerCurrency
            isCurTSumNotT = true
        }
    } else if (Objects.equals(providerCurrency, momEquivCurrency)) {
        momSum = momEquivSum
        momCurrency = momEquivCurrency
    } else if (Objects.equals(providerCurrency, momVendorFopsSumCurrency)) {
        momSum = momVendorFopsSum
        momCurrency = momVendorFopsSumCurrency
    } else {
        isAllCurNotT = true
    }
    nextRow()
    rowHeight(dataRowHeight)
    //1
    text(bookingNumberInMOM != null ? bookingNumberInMOM : dataNotInMOM, 'textData')
    nextColumn()
    //2
    text(providerOrderNumber != null ? providerOrderNumber : dataNotInProvider, 'textData')
    nextColumn()
    //3.Статус заказа от агрегатора
    text(statusInHotelAggregator != null ? statusInHotelAggregator : dataNotInProvider, 'textData')
    nextColumn()
    //4.Статус бронирования в МОМ
    text(momTicketData.status != null ? momTicketData.status.name() : dataNotInMOM, 'textData')
    nextColumn()
    //5.Дата создания бронирования в МОМ
    text(dateCreationReservationInMOM != null ? dateCreationReservationInMOM : dataNotInMOM, 'textData')
    nextColumn()
    //6.Дата создания заказа у Поставщика
    text(createOrderDateInProvider != null ? createOrderDateInProvider : dataNotInProvider, 'textData')
    nextColumn()
    //7.Дата заезда у Поставщика
    text(checkInDateInProvider != null ? checkInDateInProvider : dataNotInProvider, 'textData')
    nextColumn()
    //8.Наименование агента у Поставщика
    text(agentInProvider != null ? agentInProvider : dataNotInProvider, 'textData')
    nextColumn()
    //9.Фамилии путешественников у Постащика
    text(travellersNameInProvider != null ? travellersNameInProvider : dataNotInProvider, 'textData')
    nextColumn()
    //10.Примененные штрафы в МОМ
    text(momTicketData.penalty != null ? momTicketData.penalty.toString() : dataNotInMOM, 'textData')
    nextColumn()
    //11.Примененные штрафы у Поставщика
    text(penaltyInProvider != null ? penaltyInProvider.toString() : dataNotInProvider, 'textData')
    nextColumn()
    //12.Сумма заказа в МОМ без учета добора и сборов
    text(momSum != null ? momSum.toString() : dataNotInMOM, 'textData')
    nextColumn()
    //13.Валюта заказа в МОМ
    text(momCurrency != null ? momCurrency : dataNotInMOM, 'textData')
    nextColumn()
    //14.Сумма у Поставщика
    if (providerSum == null || providerSum == BigDecimal.valueOf(-1.0)) {
        text(dataNotInProvider, 'textData')
    } else if (statusInHotelAggregator == "CANCELLED" || (momTicketData.status == ReservationStatus.REFUND)) {
        providerSum = BigDecimal.ZERO
        text(providerSum.toString(), 'textData')
    } else {
        if (statusInHotelAggregator == "CANCELLED_WITH_PENALTY") {
            text(providerSum.add(penaltyInProvider.abs()).toString(), 'textData')
        } else {
            text(providerSum.toString(), 'textData')
        }
    }
    nextColumn()
    //15.Валюта заказа у Поставщика
    text(providerCurrency != null ? providerCurrency : dataNotInProvider, 'textData')
    nextColumn()
    //15.Сумма НДС в МОМ
    text(momSum != null
            ? (momTicketData.equivalentVatSum != null ? momTicketData.equivalentVatSum.toString() : "Без НДС")
            : dataNotInMOM,
            'textData')
    nextColumn()
    //16.Сумма НДС у Поставщика
    text((providerSum != null && providerSum != BigDecimal.valueOf(-1.0))
            ? providerVatSum != null ? providerVatSum.toString() : "Без НДС"
            : dataNotInProvider,
            'textData')
    nextColumn()
    //17.Сумма комиссии Агентства в МОМ
    number(momTicketData.commissionSum, 'numberData')
    nextColumn()
    //18.Сумма комиссии у Поставщика
    if (commissionSum == null) {
        text(dataNotInProvider, 'textData')
    } else {
        if (statusInHotelAggregator == "CANCELLED_WITH_PENALTY") {
            number(BigDecimal.ZERO, 'numberData')
        } else {
            number(commissionSum, 'numberData')
        }
    }
    nextColumn()
    //19.Примечание
    def errors = []
    if (providerOrderNumber == null) {
        errors << orderNotFoundInProvider
    }
    if (bookingNumberInMOM == null) {
        errors << orderNotFoundInMom
    }

    if (errors.empty) {
        if (providerSum == null) {
            errors.add("Суммы не сравниваются так как поставщик не вернул сумму")
        } else if (statusInHotelAggregator == "CANCELLED" || (momTicketData.status == ReservationStatus.REFUND)) {
            errors.add("Суммы не сравниваются так как один из статусов: CANCELLED, REFUND")
        } else if (isCurTSumNotT) {
            errors.add(isCurTSumNotTString)
        } else if (isAllCurNotT) {
            errors.add(isAllCurNotTString)
        } else {
            if (statusInHotelAggregator != "CANCELLED_WITH_PENALTY") {
                if (!isEqual(momSum, providerSum)) {
                    errors.add("сумма заказа в MOM не совпадает с суммой поставщика")
                }
            }
        }
        if (momVatSum != providerVatSum) {
            errors.add("Сумма НДС в МОМ не совпадает с суммой НДС поставщика")
        }
        if (commissionSum == null) {
            errors.add("Комиссии не сравниваются так как поставщик не вернул сумму комиссии")
        } else {
            if (statusInHotelAggregator == "CANCELLED_WITH_PENALTY") {
                errors.add("Комиссии не сравниваются так как статус заказа: \"Возврат со штрафом\"!")
            } else {
                if (!isEqual(momTicketData.commissionSum, commissionSum)) {
                    errors.add("сумма комиссии в MOM не совпадает c суммой комиссии поставщика")
                }
            }
        }
        if (momTicketData.status != ReservationStatus.REFUND) {
            if (!isEquals(momTicketData.checkInDate, providerData.checkInDateForEqual)) {
                errors.add('дата заезда в MOM не совпадает c датой заезда поставщика')
            }
            if (!isEquals(momTicketData.checkOutDate, providerData.checkOutDateForEqual)) {
                errors.add('дата выезда в MOM не совпадает c датой выезда поставщика')
            }
        }
    }

    if (errors.empty) {
        text('OK', 'textData')
    } else {
        text(Joiner.on(", ").join(errors), "error")
    }
}

List<MainHotelProductIndex> getOrdersFromMOM(ReservationInfo reservationInfo) {
    SearchQuery query = null
    List<MainHotelProductIndex> result
    try {
        query = new SearchQuery()
        query.getCriteria().getCriterions().add(
                SearchCriterion.eq('recordLocator', reservationInfo.recordLocator
                        ? reservationInfo.recordLocator : reservationInfo.recordLocatorProvider))
        if (parameters['provider'].toString().equalsIgnoreCase("РЦБ")) {
            query.getCriteria().getCriterions().add(
                    SearchCriterion.eq('provider', HotelProvider.RCR))
        } else {
            query.getCriteria().getCriterions().add(
                    SearchCriterion.eq('provider', provider))
        }
        result = EntityStorage.get().search(MainHotelProductIndex.class, query).getData()
        return result
    } catch (Exception ex) {
        warn("${query}" + ex)
        return null
    }
}

@CompileStatic
List<ReservationInfo> getReservationsFromAggregator() {
    List<ReservationInfo> reservations = []
    LocalDate beginDate = MiscUtil.toLocalDate(startDate)
    LocalDate endDate = MiscUtil.toLocalDate(endDate)

    long period = ChronoUnit.DAYS.between(beginDate, endDate)

    if (period <= MAX_INTERVAL) {
        return findReservations(beginDate, endDate)
    }

    int periodCount
    if (period % MAX_INTERVAL == 0) {
        periodCount = (period / MAX_INTERVAL) as int
    } else {
        periodCount = Math.round(period / MAX_INTERVAL + 0.5 as float)
    }

    for (int i = 0; i < periodCount; i++) {
        LocalDate periodBegin = beginDate
        LocalDate periodEnd = getLastPeriodDate(beginDate, endDate)
        if (periodBegin.isAfter(periodEnd)) {
            break
        }

        reservations.addAll(findReservations(periodBegin, periodEnd))
        beginDate = beginDate.plusDays(MAX_INTERVAL + 1L)
    }

    return reservations
}

List<ReservationInfo> findReservations(LocalDate periodBegin, LocalDate periodEnd) {
    def params = new FindReservationsParameters()
    params.startDate = periodBegin
    params.endDate = periodEnd
    params.gdsAccount = gdsAccount
    params.filterByParameter = (HotelProvider.OSTROVOK == provider)
            ? hotelSearchParameter : null

    def ctx = [(IBusHotelsContextKeys.HOTELS_IMPORT_BOOKING_PARAMS.name()): params]
    try {
        IntegrationBusFacade.get()
                .processRouteSync(
                        "hotels2:find-reservations:find-reservations-route", ctx)
    } catch (Exception e) {
        throw Xeption.forEndUser("Failed to get reservations from aggregator: ", e)
    }

    def result = ctx.get(IBusHotelsContextKeys.HOTELS_FIND_RESERVATIONS_RESULT.name())
    if (HotelProvider.OSTROVOK == provider && !includeNonApiOrders) {
        def login =
                gdsAccountEntity?.hotelsAggregatorData?.vendors?.get(HotelProvider.OSTROVOK)?.login

        if (TextUtil.nonBlank(login)) {
            result = result.findAll({ ReservationInfo it -> MiscUtil.equals(it.accountLogin, login) })
        }
    }

    result as List<ReservationInfo>
}

@CompileStatic
void warn(String msg) {
    def warn = binding.getProperty('warn') as Closure
    warn(msg)
}

void validateParameters() {
    if (!organizationReference) {
        throw new IllegalArgumentException("Agency is empty!")
    }
    if (!gdsAccount) {
        throw new IllegalArgumentException("Account is missing!")
    }
    if (!provider) {
        throw new IllegalArgumentException("Missing provider in account [${gdsAccount.caption}]")
    }
}

@CompileStatic
static BigDecimal negate(BigDecimal sum, ProductStatus status) {
    if (status == ProductStatus.REFUND) {
        return sum.negate()
    }
    return sum
}

@CompileStatic
class TicketData {

    String bookingNumberInMOM
    BigDecimal baseSum
    String baseSumCurrency
    BigDecimal equivalentSum
    String equivalentSumCurrency
    BigDecimal equivalentVatSum
    BigDecimal commissionSum
    ReservationStatus status
    BigDecimal penalty
    String currencyVendorFops
    BigDecimal vendorFopsSum
    Date checkInDate
    Date checkOutDate
    Date creationReservationDateInMOM
    List<String> ticketListName
    String agentInMOM
    String travellersName

    void setMoney(ProductMoneyData data) {
        this.baseSum = data.baseSum
        this.baseSumCurrency = data.baseSumCurrency
        this.equivalentSum = data.equivalentSum
        this.equivalentSumCurrency = data.equivalentSumCurrency
        this.equivalentVatSum = data.equivalentVatSum
        this.commissionSum = data.commissionSum
        this.penalty = data.penalty
        this.currencyVendorFops = data.currencyVendorFops
        this.vendorFopsSum = data.vendorFopsSum
    }
}

@CompileStatic
class MoneyWrapper {
    BigDecimal basePrice = BigDecimal.ZERO
    String basePriceCurrency
    BigDecimal equivalentPrice = BigDecimal.ZERO
    String equivalentPriceCurrency
    BigDecimal equivalentVatPrice
    BigDecimal commission = BigDecimal.ZERO
}

@CompileStatic
class ProviderClass {
    String providerOrderNumber
    String creationOrderDateInProvider
    String checkInDateInProvider
    String checkOutDateInProvider
    Date checkInDateForEqual
    Date checkOutDateForEqual
    String agentInProvider
    String travellersNameInProvider
    BigDecimal sumInProvider
    BigDecimal vatSumInProvider
    BigDecimal penaltyInProvider
    String currencyInProvider
    BigDecimal commissionInProvider
    String statusInHotelAggregator
}
