import com.gridnine.xtrip.common.model.helpers.CommonReservationGdsNameInfoHelper
import com.gridnine.xtrip.common.util.TextUtil
import groovy.transform.Field
import java.util.Date
import java.text.SimpleDateFormat
import com.gridnine.xtrip.common.model.booking.archive.ArchivedProductIndex
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.booking.archive.ArchivedBookingHelper
import com.gridnine.xtrip.common.vip.HiddenTaxHelper
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.booking.BaseProduct
import com.gridnine.xtrip.common.model.booking.air.Product
import com.gridnine.xtrip.common.model.booking.air.SegmentTariff
import com.gridnine.xtrip.common.vip.CommonVIPHelper
import com.gridnine.xtrip.common.model.booking.vip.VipClientSitePriceStructure
import java.math.BigDecimal
import com.gridnine.xtrip.common.model.booking.air.ProductFare
import com.gridnine.xtrip.common.model.helpers.AirProductHelper
import com.gridnine.xtrip.common.model.system.Money
import com.gridnine.xtrip.common.model.booking.air.Tax
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.model.dict.Airline
import com.gridnine.xtrip.common.model.dict.BaseDictionary
import java.util.Map
import java.util.Locale
import com.gridnine.xtrip.common.model.dict.GdsName
import com.gridnine.xtrip.common.model.helpers.GeneralProductHelper
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.booking.ProductStatus
import java.util.HashMap
import com.gridnine.xtrip.common.model.system.Money
import com.gridnine.xtrip.common.model.helpers.AirProductHelper
import com.gridnine.xtrip.common.l10n.model.L10nStringHelper
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.util.LocaleUtil

import java.util.stream.Collectors

createStyle(name: "title", h_alignment: "CENTER", fontBold : true)
createStyle(name: "parameterName", h_alignment: "RIGHT", fontBold : true)
createStyle(name: "parameterValue", parent: "parameterName", h_alignment: "LEFT", fontBold : false)
createStyle(name: "header", h_alignment: "CENTER", v_alignment: "CENTER", fontBold : true, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: 'textData', h_alignment: 'CENTER', v_alignment: 'CENTER', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN')
createStyle(name: 'numberData', parent: 'textData', h_alignment: 'RIGHT')
createStyle(name: 'totalTitle', parent: 'textData', fontBold: true, h_alignment: 'RIGHT')
createStyle(name: 'totalNumberData', parent: 'numberData', fontBold: true)

@Field int columnCount = 16
@Field int parameterNameColumnCount = 3
@Field int aggregatorTitleColumnCount = 6
@Field SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy")
@Field List blankOwnerRefs, agencyRefs

blankOwnerRefs = parameters['BLANK_OWNERS']
agencyRefs = parameters['AGENCIES']

int getSegmentCount(Product product) {
    int result = 0
    for (SegmentTariff tariff : product.getSegmentTariffs()) {
        result += tariff.segments.size();
    }
    return result
}

BigDecimal getSumTaxes(Product product) {
    BigDecimal result = BigDecimal.ZERO
    for (Tax tax : product.taxes) {
        BigDecimal value
        if (tax.amount != null) {
            value = tax.amount.value
        } else {
            value = tax.equivalentAmount
        }
        result = MiscUtil.sum(result, value)
    }
    return result
}

BigDecimal getEquivalentSumTaxes(Product product) {
    BigDecimal result = BigDecimal.ZERO
    for (Tax tax : product.taxes) {
        result = MiscUtil.sum(result, tax.equivalentAmount)
    }
    return result
}

class ProductStruct {
    ArchivedProductIndex index
    BookingFile bookingFile
    Product product
    BigDecimal baseFare
    String currencyCode
    BigDecimal equivalentBaseFare
    int segmentCount
    BigDecimal sumTaxes
    BigDecimal equivalentSumTaxes
}

class Aggregator {
    int ticketCount = 0, segmentCount = 0
    BigDecimal baseFare = BigDecimal.ZERO, sumTaxes = BigDecimal.ZERO
    
    void append(ProductStruct productStruct) {
        ticketCount++
        segmentCount += productStruct.segmentCount
        baseFare = MiscUtil.sum(baseFare, productStruct.equivalentBaseFare)
        sumTaxes = MiscUtil.sum(sumTaxes, productStruct.equivalentSumTaxes)
    }
}

class StatusAggregator {
    Aggregator total = new Aggregator()
    Map<ProductStatus, Aggregator> statusMap = new HashMap()
    
    void append(ProductStruct productStruct) {
        total.append(productStruct)
        Aggregator statusAggregator = statusMap.get(productStruct.product.status)
        if (statusAggregator == null) {
            statusAggregator = new Aggregator()
            statusMap.put(productStruct.product.status, statusAggregator)
        }
        statusAggregator.append(productStruct)
    }
}

ProductStruct getProductStruct(ArchivedProductIndex index) {
    BookingFile bookingFile
    def booking = EntityStorage.get().resolve(index.source)?.entity
    if (booking == null) {
        return
    }
    bookingFile = booking instanceof BookingFile ? booking : ArchivedBookingHelper.getBookingContainer(booking)?.entity
    if (bookingFile == null) {
        return null
    }
    BaseProduct baseProduct = BookingHelper.findProductByUid(index.getNavigationKey(), bookingFile)
    if (!(baseProduct instanceof Product)) {
        return null
    }
    Product product = baseProduct
    if (!blankOwnerRefs.empty && !blankOwnerRefs.contains(product.blankOwnerRef)) {
        return null
    }
    Money baseFareMoney = AirProductHelper.getBaseFare(product)
    BigDecimal equivalentBaseFare = AirProductHelper.getEquivalentFare(product)
    BigDecimal baseFare
    String currencyCode
    if (baseFareMoney != null) {
        baseFare = baseFareMoney.value
        currencyCode = baseFareMoney.currency.currencyCode
    } else {
        baseFare = equivalentBaseFare
        currencyCode = "RUB"
    }
    ProductStruct result = new ProductStruct()
    result.index = index
    result.bookingFile = bookingFile
    result.product = product
    result.baseFare = baseFare
    result.currencyCode = currencyCode
    result.equivalentBaseFare = equivalentBaseFare
    result.segmentCount = getSegmentCount(product)
    result.sumTaxes = getSumTaxes(product)
    result.equivalentSumTaxes = getEquivalentSumTaxes(product)
    return result
}

String getDate(Date date) {
    if (date == null) {
        return ""
    }
    return dateFormat.format(date)
}

def printParameter(String parameterName, String parameterValue) {
    nextRow()
    text(parameterName, "parameterName", parameterNameColumnCount, 1)
    parameterNameColumnCount.times {
        nextColumn()
    }
    text(parameterValue, "parameterValue", columnCount - parameterNameColumnCount, 1)
}

def printHeader() {
    nextRow()
    rowHeight(60, false)
    
    text("Владелец бланка", "header")
    columnWidth(19)
    
    nextColumn()
    text("Наименование субагента", "header")
    columnWidth(25)
    
    nextColumn()
    text("Статус", "header")
    columnWidth(15)
    
    nextColumn()
    text("Дата выписки", "header")
    columnWidth(12)
    
    nextColumn()
    text("Номер билета", "header")
    columnWidth(13)
    
    nextColumn()
    text("Сегменты", "header")
    columnWidth(13)
    
    nextColumn()
    text("Тариф", "header")
    columnWidth(12)
    
    nextColumn()
    text("Сумма такс", "header")
    columnWidth(14)

    nextColumn()
    text("Итого (тариф + сумма такс)", "header")
    columnWidth(15)

    nextColumn()
    text("Валюта", "header")
    columnWidth(11)

    nextColumn()
    text("Перевозчик", "header")
    columnWidth(17)
    
    nextColumn()
    text("PNR", "header")
    columnWidth(12)
    
    nextColumn()
    text("Имя пассажира", "header")
    columnWidth(46)
    
    nextColumn()
    text("Система бронирования", "header")
    columnWidth(15)
    
    nextColumn()
    text("Тип оплаты", "header")
    columnWidth(44)
    
    nextColumn()
    text("Агентство", "header")
    columnWidth(19)
}

String getProductStatus(ProductStruct productStruct) {
    return productStruct.product.status.toString()
}

BigDecimal getTotalSum(ProductStruct productStruct) {
    return MiscUtil.sum(productStruct.baseFare, productStruct.sumTaxes)
}

String getBaseDictionary(BaseDictionary dictionary) {
    String ruValue = null, enValue = null, anyValue = null
    for (Map.Entry entry : dictionary.translations.entrySet()) {
        Locale locale = entry.key
        if (locale.country == "ru") {
            ruValue = entry.value
        } else if (locale.country == "en") {
            enValue = entry.value
        }
        anyValue = entry.value
    }
    if (ruValue != null) {
        return ruValue
    }
    if (enValue != null) {
        return enValue
    }
    return anyValue
}

String getCarrier(ProductStruct productStruct) {
    if (productStruct.product.carrier == null) {
        return null
    }
    DictionaryCache dictCache = Environment.getPublished(DictionaryCache.class)
    Airline carrier = dictCache.resolveReference(productStruct.product.carrier)
    if (carrier == null) {
        return null
    }
    return getBaseDictionary(carrier)
}

String getPnr(ProductStruct productStruct) {
    if (TextUtil.nonBlank(productStruct.index.displayedRecordLocator)) {
        return productStruct.index.displayedRecordLocator
    }
    return productStruct.index.recordLocator
}

String getGdsName(ProductStruct productStruct) {
    return CommonReservationGdsNameInfoHelper.getDisplayedGdsName(productStruct.product.reservation)?.toString()
}

String getPaymentType(ProductStruct productStruct) {
    return GeneralProductHelper.findFop(productStruct.product)?.type?.toString()
}

@Field Map organizationNameCache = [:]
String getOrganizationName(EntityReference organizationRef) {
    if (organizationRef == null) {
        return null
    }
    if (!organizationNameCache.containsKey(organizationRef)) {
        Organization organization = EntityStorage.get().resolve(organizationRef)?.entity
        String result
        if (organization == null) {
            result = null
        } else {
            if (organization.shortName != null) {
                result = L10nStringHelper.getValue(organization.shortName, LocaleUtil.LOCALE_RU, false)
            } else if (organization.fullName != null) {
                result = L10nStringHelper.getValue(organization.fullName, LocaleUtil.LOCALE_RU, false)
            }
        }
        organizationNameCache[organizationRef] = result
        return result
    }
    return organizationNameCache[organizationRef]
}

String getSubagency(ProductStruct productStruct) {
    return getOrganizationName(productStruct.index.subagency)
}

String getAgency(ProductStruct productStruct) {
    return getOrganizationName(productStruct.index.agency)
}

def printProduct(ProductStruct productStruct) {
    nextRow()
    text(productStruct.product.blankOwnerRef.caption, "textData")
    
    nextColumn()
    text(getSubagency(productStruct), "textData")
    
    nextColumn()
    text(getProductStatus(productStruct), "textData")
    
    nextColumn()
    text(getDate(productStruct.product.issueDate), "textData")
    
    nextColumn()
    text(productStruct.product.systemNumber, "textData")
    
    nextColumn()
    number(productStruct.segmentCount, "numberData")

    nextColumn()
    number(productStruct.baseFare, "numberData")
    
    nextColumn()
    number(productStruct.sumTaxes, "numberData")
    
    nextColumn()
    number(getTotalSum(productStruct), "numberData")
    
    nextColumn()
    text(productStruct.currencyCode, "textData")
    
    nextColumn()
    text(getCarrier(productStruct), "textData")
    
    nextColumn()
    text(getPnr(productStruct), "textData")
    
    nextColumn()
    text(productStruct.product?.traveller?.name, "textData")
    
    nextColumn()
    text(getGdsName(productStruct), "textData")
    
    nextColumn()
    text(getPaymentType(productStruct), "textData")
    
    nextColumn()
    text(getAgency(productStruct), "textData")
}

BigDecimal getTotal(Aggregator aggregator) {
    return MiscUtil.sum(aggregator.baseFare, aggregator.sumTaxes)
}

def printAggregator(String title, Aggregator aggregator) {
    nextRow()
    text(title, "totalTitle", aggregatorTitleColumnCount, 1)
    aggregatorTitleColumnCount.times {
        nextColumn()
    }
    number(aggregator.baseFare, "totalNumberData")
    nextColumn()
    number(aggregator.sumTaxes, "totalNumberData")
    nextColumn()
    number(getTotal(aggregator), "totalNumberData")
}

/////////////////////////////////////////

boolean isSameGroup(ProductStruct productStruct1, ProductStruct productStruct2) {
    return productStruct1.product.getValue("subagency") == productStruct2.product.getValue("subagency")
}

def printGroupTotal(ProductStruct productStruct, StatusAggregator groupAggregator) {
    String groupName = getOrganizationName(productStruct.index.subagency)
    groupAggregator.statusMap.each { ProductStatus status, Aggregator aggregator ->
        printAggregator("Итого операция ${status} по ${groupName}:", aggregator)
    }
    printAggregator("Итого по ${groupName}:", groupAggregator.total)
}

text("Отчет по авиа билетам", "title", columnCount, 1)
printParameter("Дата составления отчета:", getDate(new Date()))
printParameter("Период:", parameters['REPORT_PERIOD'])
if (!blankOwnerRefs.empty) {
    printParameter("Владельцы бланков:", blankOwnerRefs.toString())
}
if (!agencyRefs.empty) {
    printParameter("Агентства:", agencyRefs.toString())
}
nextRow()
printHeader()
ProductStruct previousProductStruct = null
StatusAggregator groupAggregator = new StatusAggregator()
allTickets.each{ ArchivedProductIndex index ->
    ProductStruct productStruct = getProductStruct(index)
    if (productStruct == null) {
        return
    }
    printProduct(productStruct)
    if (previousProductStruct != null && !isSameGroup(previousProductStruct, productStruct)) {
        printGroupTotal(previousProductStruct, groupAggregator)
        groupAggregator = new StatusAggregator()
    }
    groupAggregator.append(productStruct)
    previousProductStruct = productStruct
}
if (previousProductStruct != null) {
    printGroupTotal(previousProductStruct, groupAggregator)
}
