package com.gridnine.xtrip.server.reports.templates

import com.google.common.base.Joiner
import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.hotels.model.*
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.booking.CurrencyRateType
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.ReservationType
import com.gridnine.xtrip.common.model.booking.WorkflowStatus
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProduct
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProvider
import com.gridnine.xtrip.common.model.booking.xtriphotels.MainHotelProductIndex
import com.gridnine.xtrip.common.model.dict.PreferenceKey
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.helpers.DictHelper
import com.gridnine.xtrip.common.model.helpers.FinanceHelper
import com.gridnine.xtrip.common.model.helpers.HotelProductHelper
import com.gridnine.xtrip.common.model.system.Money
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil
import com.gridnine.xtrip.server.hotels.connector.HotelsAggregatorConnector
import groovy.transform.Field

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')
createStyle(name: 'numberData',parent: 'data',h_alignment: 'RIGHT')
createStyle(name: 'error', foreground: 'YELLOW', h_alignment : 'CENTER')
createStyle(name: 'errorMessage', h_span: 7, v_alignment: 'CENTER', h_alignment: 'LEFT', fontHeight:8)


def titleRowHeight = 30
def headerRowHeight = 40
def dataRowHeight = 12
def errorMessageRowHeight = 10

def statusMap = [:]
statusMap[ProductStatus.SELL] = HotelReservationStatus.CONFIRMED
statusMap[ProductStatus.BOOKING] = HotelReservationStatus.CONFIRMED
statusMap[ProductStatus.REFUND] = HotelReservationStatus.CANCELLED

def connector = Environment.getPublished(HotelsAggregatorConnector.class)
SearchReservationsParameters searchParameters = new SearchReservationsParameters()
connector.fillBaseParameters searchParameters
searchParameters.provider = parameters['provider']
searchParameters.startDate = parameters['key-report-params']['periodBegin']
searchParameters.endDate = parameters['key-report-params']['periodEnd']
SearchReservationsResult result = connector.searchReservations(searchParameters)

@Field String equivCurrCode = DictHelper.getPreferenceValue(PreferenceKey.EQUIVE_CURRENCY, null)
@Field BigDecimal eps = new BigDecimal("0.00001")

BigDecimal getEquivalentSum(Date date, BigDecimal amount, String currCode) {
    if (currCode == equivCurrCode) {
        return amount
    } else {
        return FinanceHelper.exchange(amount, currCode, equivCurrCode, CurrencyRateType.CBR, null, null, true)
    }
}

BigDecimal getBaseSum(ReservationInfo reservation, Date date, String equivCurrCode) {
    if (date == null) {
        date = reservation.registrationDate
    }
    if (parameters['provider'] == HotelProvider.GTA) {
        return reservation.basePrice.value
    }
    BigDecimal result = getEquivalentSum(date, //
        reservation.basePrice.value, //
        reservation.basePrice.currency.currencyCode)
    return result
}

BigDecimal getCommissionSum(ReservationInfo reservation, Date date, String equivCurrCode) {
    if (date == null) {
        date = reservation.registrationDate
    }
    if (parameters['provider'] == HotelProvider.GTA) {
        return reservation.hotelCommission.amount.value
    }
    return getEquivalentSum(date, //
        reservation.hotelCommission.amount.value, //
        reservation.hotelCommission.amount.currency.currencyCode)
}

boolean isEqual(BigDecimal a, BigDecimal b) {
    return isZero(a.subtract(b))
}

boolean isZero(BigDecimal value) {
    return value.abs() < eps
}


Map<String, List<MainHotelProductIndex>> ticketMap = [:].withDefault {[]}
tickets { MainHotelProductIndex prod ->
    if (TextUtil.nonBlank(prod.displayedRecordLocator)) {
        ticketMap[prod.displayedRecordLocator].add(prod)
    } else {
        ticketMap[prod.recordLocator].add(prod)
    }
}

nextRow()
rowHeight(titleRowHeight)
text('Поставщик: ' +  parameters['provider'], 'title')

nextRow()
rowHeight(headerRowHeight)

text('Номер заказа МОМ', 'header')
columnWidth(22)

nextColumn()
text('Номер заказа поставщика', 'header')
columnWidth(22)

nextColumn()
text('Сумма заказа в МОМ без учета добора и сборов', 'header')
columnWidth(30)

nextColumn()
text('Сумма поставщика', 'header')
columnWidth(15)

nextColumn()
text('Сумма комиссии Випсервис в МОМ', 'header')
columnWidth(20)

nextColumn()
text('Сумма комиссии поставщика', 'header')
columnWidth(20)

nextColumn()
text('Примечание', 'header')
columnWidth(120)

class TicketData {
    BigDecimal baseSum
    BigDecimal commissionSum
    Date checkInDate, checkOutDate
    WorkflowStatus status
}

@Field Map bookingCntCache = [:]
EntityContainer getBookingCnt(MainHotelProductIndex productIndex) {
    EntityContainer result = bookingCntCache[productIndex.source]
    if (result == null) {
        result = EntityStorage.get().resolve(productIndex.source)
        bookingCntCache[productIndex.source] = result
    }
    return result
}

@Field Map productCache = [:]
HotelProduct getProduct(MainHotelProductIndex productIndex) {
    return BookingHelper.findProductByUid(productIndex.navigationKey, getBookingCnt(productIndex).entity)
}

BigDecimal getMomBaseSum(MainHotelProductIndex productIndex) {
    if (parameters['provider'] == HotelProvider.GTA) {
        HotelProduct product = getProduct(productIndex)
        Money money = HotelProductHelper.calcBasePrice(product)
        BigDecimal result = money.value
        if (productIndex.status == ProductStatus.REFUND) {
            result = result.negate()
        }
        return result
    } else {
        return productIndex.baseSum
    }
}

TicketData getTicketData(List<MainHotelProductIndex> ticketList) {
    TicketData result = new TicketData()
    result.status = ticketList.get(0).bookingWorkflowStatus
    result.baseSum = ticketList.sum(BigDecimal.ZERO){ getMomBaseSum(it) }
    result.commissionSum = ticketList.sum(BigDecimal.ZERO) {
        switch (it.status) {
            case ProductStatus.EXCHANGE:
                case ProductStatus.REFUND: return -it.hotelComissionSum
            case ProductStatus.VOID: return 0
            default: return it.hotelComissionSum
        }
    }
    MainHotelProductIndex lastSell = ticketList.find {MainHotelProductIndex it ->
        it.status == ProductStatus.SELL && it.nextProductSystemNumber == null
    }
    if (lastSell != null) {
        result.checkInDate = lastSell.hotelCheckInDate
        result.checkOutDate = lastSell.hotelCheckOutDate
    } else {
        result.checkInDate = ticketList.min { MainHotelProductIndex it ->
            it.hotelCheckInDate.getTime()
        }.hotelCheckInDate
    
        result.checkOutDate = ticketList.max {MainHotelProductIndex it ->
            it.hotelCheckOutDate.getTime()
        }.hotelCheckOutDate
    }

    return result
}

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

List<ReservationInfo> reservations
if (parameters['provider'] == HotelProvider.ACADEMSERVICE) {
    reservations = []
    for (ReservationInfo reservationInfo : result.reservations) {
        if (reservationInfo.contactPerson?.name == 'XML') {
            reservations.add(reservationInfo)
        }
    }
} else {
    reservations = result.reservations
}
Map<String, ReservationInfo> resByPnr = reservations.collectEntries {[it.recordLocator, it]}

List<ReservationInfo> resNotInMom = reservations
        .grep { !ticketMap.containsKey(it.recordLocator) }
        .sort(false){it.recordLocator}.reverse()

Map<String, List<MainHotelProductIndex>> ticketsInMomAndProvider = ticketMap.findAll {resByPnr.containsKey(it.key)}
Map<String, List<MainHotelProductIndex>> ticketsNotInProvider = ticketMap.findAll {!resByPnr.containsKey(it.key)}

String getSupplierBookingNumber(ReservationInfo info) {
    if (parameters['provider'] == HotelProvider.OKTOGO) {
        return info.recordLocator
    } else {
        return info.recordLocatorProvider
    }
}

ticketsInMomAndProvider.entrySet().sort(false){it.key}.reverse().each { entry ->

    pnr = entry.key
    resInfo = resByPnr[pnr]
    ticketsInBooking = entry.value

    if (hasCallCenter(ticketsInBooking)) {
        return
    }

    TicketData momTicketData = getTicketData(ticketsInBooking)

    Date bookingCreationDate = ticketsInBooking[0].bookingCreationDate
    String bookingNumber = ticketsInBooking[0].bookingNumber

    BigDecimal baseSum = getBaseSum(resInfo, bookingCreationDate, equivCurrCode)
    BigDecimal commissionSum = getCommissionSum(resInfo, bookingCreationDate, equivCurrCode)

    nextRow()
    rowHeight(dataRowHeight)

    text(bookingNumber, 'textData')
    nextColumn()

    text(getSupplierBookingNumber(resInfo), 'textData')
    nextColumn()

    number(momTicketData.baseSum, 'numberData')
    nextColumn()

    number(baseSum, 'numberData')
    nextColumn()

    number(momTicketData.commissionSum, 'numberData')
    nextColumn()

    number(commissionSum, 'numberData')
    nextColumn()

    def errors = []
    if (!isEqual(momTicketData.baseSum,baseSum)) {
        errors.add("сумма заказа в MOM не совпадает с суммой поставщика")
    }
    if (!isEqual(momTicketData.commissionSum,commissionSum)) {
        errors.add("сумма комиссии в MOM не совпадает c суммой комиссии поставщика")
    }
    
    if (momTicketData.status != WorkflowStatus.REFUND && momTicketData.status != WorkflowStatus.CANCELLED) {
        if (!isEquals(momTicketData.checkInDate, resInfo.checkInDate)) {
            errors.add('дата заезда в MOM не совпадает c датой заезда поставщика')
        }
        if (!isEquals(momTicketData.checkOutDate, resInfo.checkOutDate)) {
            errors.add('дата выезда в MOM не совпадает c датой выезда поставщика')
        }
    }
    

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

boolean isEquals(Date date1, Date date2) {
    return MiscUtil.clearTime(date1) == MiscUtil.clearTime(date2)
}

resNotInMom.each { resInfo ->

    nextRow()
    rowHeight(dataRowHeight)

    text('', 'textData')
    nextColumn()

    text(getSupplierBookingNumber(resInfo), 'textData')
    nextColumn()

    text('', 'textData')
    nextColumn()

    number(resInfo.basePrice.value, 'numberData')
    nextColumn()

    text('', 'textData')
    nextColumn()
    
    number(resInfo.hotelCommission.amount.value, 'numberData')
    nextColumn()

    text('Бронирование найдено у поставщика, но не найдено в МОМ', 'error')
}

ticketsNotInProvider.entrySet().sort(false){it.key}.reverse().each { entry ->

    List<MainHotelProductIndex> ticketsInBooking = entry.value
    if (hasCallCenter(ticketsInBooking)) {
        return
    }

    Date bookingCreationDate = ticketsInBooking[0].bookingCreationDate
    String bookingNumber = ticketsInBooking[0].bookingNumber

    ticketData = getTicketData(ticketsInBooking)

    nextRow()
    rowHeight(dataRowHeight)

    text(bookingNumber, 'textData')
    nextColumn()

    text('', 'textData')
    nextColumn()

    number(ticketData.baseSum, 'numberData')
    nextColumn()

    text('', 'textData')
    nextColumn()

    number(ticketData.commissionSum, 'numberData')
    nextColumn()

    text('', 'textData')
    nextColumn()

    text('Бронирование найдено в МОМ, но не найдено у поставщика', 'error')
}

if (!result.error.empty) {
    nextRow()
    nextRow()
    nextRow()
    rowHeight(errorMessageRowHeight)
    text("при получении данных от поставщика возникли следующие ошибки:", "errorMessage")
    for (String error : result.error) {
        nextRow()
        rowHeight(errorMessageRowHeight)
        text(error, "errorMessage")
    }
}