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

import java.util.List
import java.util.Set
import java.util.Arrays

import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.rules.RuleSettings
import com.gridnine.xtrip.common.rules.RuleResult
import com.gridnine.xtrip.common.gds.sabre.model.SabreGdsAccount
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.rules.RuleRestriction
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.rules.RuleResult
import com.gridnine.xtrip.common.gds.rules.Actions
import com.gridnine.xtrip.common.rules.elements.RuleProperty
import com.gridnine.xtrip.common.gds.model.DailySalesReportParameters
import com.gridnine.xtrip.common.gds.model.DailySalesReportItem

import java.util.Date

import com.gridnine.xtrip.common.model.dict.GdsName

import groovy.transform.Field
import groovy.transform.EqualsAndHashCode

import com.gridnine.xtrip.common.midoffice.model.SalesContext
import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.server.ibus.IntegrationBusFacade
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.ProductIndex
import com.gridnine.xtrip.common.model.system.PaymentType
import com.google.common.base.Joiner
import java.math.BigDecimal
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.search.SearchCriteria
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.model.booking.air.Product
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.helpers.AirProductHelper
import com.gridnine.xtrip.common.model.booking.air.Tax

createStyle(name: 'textData', h_alignment: 'CENTER', v_alignment: 'BOTTOM', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', 'wrapText' : true)
createStyle(name: 'numberData', parent: 'textData', h_alignment: 'RIGHT')
createStyle(name: 'total', h_alignment: 'RIGHT', fontBold : true, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM')
createStyle(name: 'splitedData', parent: 'textData', leftBorder:'MEDIUM')
createStyle(name: 'splitedNumberData', parent: 'numberData', leftBorder:'MEDIUM')
createStyle(name: 'parameterValue', h_alignment: 'LEFT', v_alignment: 'BOTTOM', 'wrapText' : true)

@Field SalesContext salesContext = new SalesContext()
salesContext.salesPoint = parameters['SALES_POINT']

@Field Joiner commaJoiner = Joiner.on(', ')

@Field Map<EntityReference, EntityContainer> entityStorageCache = [:]
EntityContainer getContainer(EntityReference ref) {
    if (entityStorageCache.containsKey(ref)) {
        return entityStorageCache[ref]
    }
    EntityContainer result = EntityStorage.get().resolve(ref)
    entityStorageCache[ref] = result
    return result
}

class PccStruct {
    EntityContainer<SabreGdsAccount> gdsAccountCnt
    String pcc
    
    public boolean equals(Object val) {
        if (val?.getClass() != this.getClass()) {
            return false
        }
        PccStruct other = val
        return gdsAccountCnt == other.gdsAccountCnt && pcc == other.pcc
    }
    
    public int hashCode() {
        return Arrays.hashCode([gdsAccountCnt, pcc]);
    }
    
    public String toString() {
        return "{pcc=${pcc}, gdsAccount=${gdsAccountCnt.entity.name}[${gdsAccountCnt.uid}]}"
    }
}

class GdsTicket {
    String ticketNumber
    ProductStatus status
    BigDecimal price
    int conjCount
    String passengerName
    
    String getCarrierNumber() {
        return ticketNumber.substring(0, 3)
    }
    
    String getNumber() {
        return ticketNumber.substring(3)
    }
}

class MomTicket {
    String bookingNumber
    String carrierNumber
    String number
    ProductStatus status
    BigDecimal price
    int conjCount
    String passengerName
    
    String getTicketNumber() {
        return "${carrierNumber}${number}"
    }
}

@EqualsAndHashCode
class TicketKey {
    String ticketNumber
    ProductStatus status
}

TicketKey getTicketKey(MomTicket ticket) {
    TicketKey result = new TicketKey()
    result.ticketNumber = ticket.ticketNumber
    result.status = ticket.status
    return result
}

TicketKey getTicketKey(GdsTicket ticket) {
    TicketKey result = new TicketKey()
    result.ticketNumber = ticket.ticketNumber
    result.status = ticket.status
    return result
}

class Aggregator {
    BigDecimal momPrice = BigDecimal.ZERO
    BigDecimal gdsPrice = BigDecimal.ZERO
    
    void append(MomTicket momTicket, GdsTicket gdsTicket) {
        momPrice = MiscUtil.sum(momPrice, momTicket?.price)
        gdsPrice = MiscUtil.sum(gdsPrice, gdsTicket?.price)
    }
    
    BigDecimal getPriceDiff() {
        return MiscUtil.sum(momPrice, gdsPrice?.negate())
    }
}

Set<PccStruct> getPccStructs() {
    Set<PccStruct> result = [] as Set
    for (RuleSettings ruleSettings : EntityStorage.get().resolve(parameters['SALES_POINT']).entity.ruleSet.rules) {
        List<EntityContainer<SabreGdsAccount>> gdsAccountCnts = []
        for (RuleRestriction restriction : ruleSettings.restrictions) {
            if (restriction.propertyId == 'GDS_ACCOUNT') {
                for (EntityReference<?> gdsAccountRef : restriction.value) {
                    EntityContainer<?> gdsAccountCnt = EntityStorage.get().resolve(gdsAccountRef);
                    if (gdsAccountCnt?.entity instanceof SabreGdsAccount) {
                        gdsAccountCnts.add(gdsAccountCnt)
                    }
                }
            }
        }
        if (gdsAccountCnts.empty) {
            continue
        }
        List<String> pccs = []
        for (RuleResult ruleResult : ruleSettings.results) {
            if (ruleResult.actionId == 'TICKETING_PCC') {
                pccs.add(ruleResult.value)
            }
        }
        if (pccs.empty) {
            continue
        }
        for (EntityContainer<SabreGdsAccount> gdsAccountCnt : gdsAccountCnts) {
            for (String pcc : pccs) {
                PccStruct pccStruct = new PccStruct()
                pccStruct.gdsAccountCnt = gdsAccountCnt
                pccStruct.pcc = pcc
                result.add(pccStruct)
            }
        }
    }
    return result
}

List<GdsTicket> loadGdsTickets(PccStruct pccStruct, Date date) {
    DailySalesReportParameters dailySalesReportParameters = new DailySalesReportParameters()
    dailySalesReportParameters.date = date
    dailySalesReportParameters.pcc = pccStruct.pcc
    dailySalesReportParameters.includeVoided = true
    dailySalesReportParameters.includeRefunded = true
    dailySalesReportParameters.trace = parameters['TRACE']
    Map<String, Object> parameters = [:]
    parameters['GDS'] = GdsName.SABRE
    parameters['GDS_ACCOUNT'] = pccStruct.gdsAccountCnt
    parameters['DAILY_SALES_REPORT_PARAMETERS'] = dailySalesReportParameters
    parameters['SALES_CONTEXT'] = salesContext
    try {
        Environment.getPublished(IntegrationBusFacade.class).getRequestReplyAdapter('gds:sabre:daily-sales-report:daily-sales-report').processSync(parameters)
    } catch (Exception e) {
        warn ("pccStruct ${pccStruct} failed: ${e}")
        return []
    }
    List<GdsTicket> result = []
    for (DailySalesReportItem item : parameters['DAILY_SALES_REPORT_ITEMS']) {
        GdsTicket gdsTicket = new GdsTicket()
        gdsTicket.ticketNumber = item.ticketNumber
        gdsTicket.status = item.status
        if (item.status == ProductStatus.SELL) {
            gdsTicket.price = item.price
        } else if (item.status == ProductStatus.VOID) {
            gdsTicket.price = BigDecimal.ZERO;
        } else if (item.status == ProductStatus.REFUND) {
            gdsTicket.price = item.price.negate()
        }
        gdsTicket.conjCount = item.conjCount
        gdsTicket.passengerName = item.passenger
        result.add(gdsTicket)
    }
    return result
}

Product getProduct(ProductIndex index) {
    BookingFile booking = getContainer(index.source)?.entity
    if (booking == null) {
        return null
    }
    return BookingHelper.findProductByUid(index.navigationKey, booking)
}

BigDecimal getTaxPenalty(Product product) {
    BigDecimal result = BigDecimal.ZERO
    for (Tax tax : product.taxes) {
        if (AirProductHelper.isPenaltyTax(null, tax)) {
            result = MiscUtil.sum(result, tax.equivalentAmount)
        }
    }
    if (product.previousProduct?.status == ProductStatus.EXCHANGE) {
        for (Tax tax : product.previousProduct.taxes) {
            if (AirProductHelper.isPenaltyTax(null, tax)) {
                result = MiscUtil.sum(result, tax.equivalentAmount?.negate())
            }
        }
    }
    result
}

MomTicket getMomTicket(ProductIndex index) {
    Product product = getProduct(index)
    MomTicket result = new MomTicket()
    result.bookingNumber = index.bookingNumber
    result.carrierNumber = product?.carrierNumber
    result.number = product.systemNumber
    result.status = index.status
    if (product.status == ProductStatus.SELL && product?.previousProduct?.status == ProductStatus.EXCHANGE) {
        result.price = MiscUtil.sum(BigDecimal.ZERO, product.addCollectEquivalent, AirProductHelper.calculateTaxesEquivalentAddCollect(product), product.penalty, getTaxPenalty(product))
    } else if (index.status == ProductStatus.SELL || index.status == ProductStatus.REFUND) {
        result.price = index.totalSum
    } else if (index.status == ProductStatus.VOID) {
        result.price = BigDecimal.ZERO
    }
    result.conjCount = index.conjCount
    result.passengerName = index.travellerName
    return result
}

@Field int ticketCounter = 0
def printTickets(MomTicket momTicket, GdsTicket gdsTicket) {
    nextRow()
    rowAutoHeight()
    
    number(++ticketCounter, 'numberData')
    
    String carrierNumber = momTicket?.carrierNumber ?: gdsTicket?.carrierNumber
    String num = momTicket?.number ?: gdsTicket?.number
    
    nextColumn()
    text(carrierNumber, 'textData')
    
    nextColumn()
    text(num, 'textData')
    
    if (momTicket == null) {
        nextColumn()
        text(null, 'splitedData')
        
        3.times {
            nextColumn()
            text(null, 'textData')
        }
    } else {
        nextColumn()
        text(momTicket.passengerName, 'splitedData')
        
        nextColumn()
        text(momTicket.bookingNumber, 'textData')
        
        nextColumn()
        text(momTicket.status?.toString(), 'textData')
        
        nextColumn()
        number(momTicket.price, 'numberData')
    }
    
    if (gdsTicket == null) {
        nextColumn()
        text(null, 'splitedData')
        
        2.times {
            nextColumn()
            text(null, 'textData')
        }
    } else {
        nextColumn()
        text(gdsTicket.passengerName, 'splitedData')
        
        nextColumn()
        text(gdsTicket.status?.toString(), 'textData')
        
        nextColumn()
        number(gdsTicket.price, 'numberData')
    }
    
    nextColumn()
    number(MiscUtil.sum(momTicket?.price, gdsTicket?.price?.negate()), 'splitedNumberData')
}

boolean isTicketInDifferentSalesPoint(String number) {
    SearchQuery query = new SearchQuery()
    query.criteria.criterions.add(SearchCriterion.contains(ProductIndex.Property.ticketNumbers.name(), number))
    List<ProductIndex> products = EntityStorage.get().search(ProductIndex.class, query).data
    if (products.empty) {
        return false
    }
    for (ProductIndex product : products) {
        if (product.salesPoint == parameters['SALES_POINT']) {
            return false
        }
    }
    return true
}

def printAggregator(Aggregator aggregator) {
    nextRow()
    
    text('Итого:', 'total', 3, 1)
    3.times { nextColumn() }
    
    3.times {
        number(null, 'total')
        nextColumn()
    }
    
    number(aggregator.momPrice, 'total')
    nextColumn()
    
    2.times {
        number(null, 'total')
        nextColumn()
    }
    
    number(aggregator.gdsPrice, 'total')
    nextColumn()
    
    number(aggregator.priceDiff, 'total')
}

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

switchPage('Sheet1')

int parameterNameColumnsNumber = 4
int parameterValueColumnsNumber = 7

moveTo(0, parameterNameColumnsNumber)
text(parameters['REPORT_PERIOD'], 'parameterValue', parameterValueColumnsNumber, 1)

moveTo(3, 0)
Map<TicketKey, MomTicket> momTicketsCache = [:]
Aggregator aggregator = new Aggregator()
tickets { ProductIndex index ->
    MomTicket momTicket = getMomTicket(index)
    if (momTicket.number == null) {
        printTickets(momTicket, null)
        aggregator.append(momTicket, null)
    } else {
        TicketKey key = getTicketKey(momTicket)
        if (momTicketsCache.containsKey(key)) {
            printTickets(momTicket, null)
            aggregator.append(momTicket, null)
        } else {
            momTicketsCache[key] = momTicket
        }
    }
}

Set<PccStruct> pccStructs = getPccStructs()
for (PccStruct pccStruct : pccStructs) {
    info ("pccStruct: ${pccStruct}")
}

Date startDate = MiscUtil.clearTime(parameters['key-report-params']['periodBegin'])
Date endDate = MiscUtil.setDayEndTime(parameters['key-report-params']['periodEnd'])
for (Date date = startDate; !date.after(endDate); date = MiscUtil.addDaysToDate(date, 1)) {
    for (PccStruct pccStruct : pccStructs) {
        for (GdsTicket gdsTicket : loadGdsTickets(pccStruct, date)) {
            TicketKey key = getTicketKey(gdsTicket)
            MomTicket momTicket = momTicketsCache.remove(key)
            if (momTicket == null) {
                if (!isTicketInDifferentSalesPoint(gdsTicket.number)) {
                    printTickets(null, gdsTicket)
                    aggregator.append(null, gdsTicket)
                }
            } else {
                printTickets(momTicket, gdsTicket)
                aggregator.append(momTicket, gdsTicket)
            }
        }
    }
}

for (MomTicket momTicket : momTicketsCache.values()) {
    printTickets(momTicket, null)
    aggregator.append(momTicket, null)
}
printAggregator(aggregator)