import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.agencymemo.AgencyMemoProductType
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.parsers.model.BSPExchangeDocumentIndex
import com.gridnine.xtrip.common.parsers.model.Tax
import com.gridnine.xtrip.common.parsers.model.TransactionType
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicket
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicketType
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.util.BooleanUtil
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil

//CREATING STYLES
createStyle(name: 'title',fontBold: true, h_span: 13, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:20)
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: 'dateData',parent: 'data',h_alignment: 'RIGHT',format: 'm/d/yy')
createStyle(name: 'subtotalTitle', parent: 'header', h_span: 6, h_alignment: 'RIGHT', fontBold: true)
createStyle(name: 'subtotalNumber',parent: 'subtotalTitle',  h_span: 1)
createStyle(name: 'totalTitle', parent: 'subtotalTitle', h_span: 3)
createStyle(name: 'totalNumber',parent: 'totalTitle')

boolean notCheckValidators = isParamTrue("not_check_validators")
boolean notCheckCommissions = isParamTrue("not_check_commissions")
boolean notCheckIssueDates = isParamTrue("not_check_issue_dates")
boolean excludeZeroPriceTickets = isParamTrue("exclude_zero_price_tickets")

//supplementary functions
def notNullNumber = {
    return it ?: BigDecimal.ZERO
}

def dateToLong = {
    if(!it){
        return 0
    }
    return MiscUtil.clearTime(it).getTime()
}

def equalsByTariff = { BSPWrapper t1, BSPWrapper t2 ->
    BigDecimal diff = notNullNumber(t1.equivalentFare)
    BigDecimal diffTax = notNullNumber(t1.taxesSum)
    BigDecimal diffCommissionValue = notNullNumber(t1.commissionValue)

    BigDecimal diff2 = notNullNumber(t2.equivalentFare)
    BigDecimal diffTax2 = notNullNumber(t2.taxesSum)
    BigDecimal diffCommissionValue2 = notNullNumber(t2.commissionValue)

    if (diff2.compareTo(BigDecimal.ZERO) != 0){
        if (diff2.compareTo(diff) == 0) {
            return true
        }
        if (diff2.compareTo(diffTax) == 0) {
            t1.taxesSum = BigDecimal.ZERO
            return true
        }
        if (diff2.compareTo(diffCommissionValue) == 0) {
            t1.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diff.compareTo(BigDecimal.ZERO) != 0){
        if (diff.compareTo(diff2) == 0) {
            return true
        }
        if (diff.compareTo(diffTax2) == 0) {
            t2.taxesSum = BigDecimal.ZERO
            return true
        }
        if (diff.compareTo(diffCommissionValue2) == 0) {
            t2.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diffTax2.compareTo(BigDecimal.ZERO) != 0){
        if (diffTax2.compareTo(diffCommissionValue) == 0) {
            t2.taxesSum = BigDecimal.ZERO
            t1.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diffTax.compareTo(BigDecimal.ZERO) != 0) {
        if (diffTax.compareTo(diffCommissionValue2) == 0) {
            t1.taxesSum = BigDecimal.ZERO
            t2.commissionValue = BigDecimal.ZERO
            return true
        }
    }
    return diff.compareTo(diff2) == 0
}


def equalsByCommissionRate = { BSPWrapper t1, BSPWrapper t2 ->
    if (notNullNumber(t1.commissionValue).compareTo(notNullNumber(t2.commissionValue)) == 0) {
        return true
    } else {
        return notNullNumber(t1.commissionRate).compareTo(notNullNumber(t2.commissionRate)) == 0
    }
}

def equalsByCommissionValue = { BSPWrapper t1, BSPWrapper t2 ->
    return notNullNumber(t1.commissionValue).compareTo(notNullNumber(t2.commissionValue)) == 0
}

def equalsByTaxesSum = { BSPWrapper t1, BSPWrapper t2 ->
    return notNullNumber(t1?.taxesSum).compareTo(notNullNumber(t2?.taxesSum)) == 0
}

def equalsByValidator = { BSPWrapper t1, BSPWrapper t2 ->
    if (t1?.status == ProductStatus.VOID || t2?.status == ProductStatus.VOID) {
        return true
    } else {
        return TextUtil.isSame(t1?.validator, t2?.validator)
    }
}

def equalsByDate = { BSPWrapper t1, BSPWrapper t2 ->
    return dateToLong(t1.operationDate) == dateToLong(t2.operationDate)
}

def transactionTypeToStatus = {TransactionType type ->
    if(type == TransactionType.REFUND){
        return ProductStatus.REFUND
    }
    if(type == TransactionType.CANCEL){
        return ProductStatus.VOID
    }
    return ProductStatus.SELL
}

def getConjTickets(AirTicketsTemplateReportTicket prod) {
    if (!prod?.ticketNumber) {
        return Collections.emptyList()
    }
    long number = prod.ticketNumber as long
    List<String> result = new ArrayList<>()
    for (int i = 0; i < prod.conjCount; i++) {
        result.add(++number as String)
    }
    return result
}

// Air ticket
def createWrapperFromProduct = {AirTicketsTemplateReportTicket prod ->
    if (prod == null) return
    BSPWrapper result = new BSPWrapper()
    result.source = "AGENCY"
    result.ticketNumber = prod.ticketNumber
    result.conjCount = prod.conjCount ?: 0
    result.conjTickets.addAll(getConjTickets(prod))
    result.operationDate = prod.issueDate
    result.commissionRate = prod.vendorCommissionRate
    result.commissionValue = prod.vendorCommissionValue
    if (prod.type == AirTicketsTemplateReportTicketType.MEMO) {
        result.status = ProductStatus.SELL
        result.equivalentFare = prod.agencyMemoProductTariffEquivalentAmount ? prod.agencyMemoProductTariffEquivalentAmount : BigDecimal.ZERO
        // always positive for ACM and always negative for ADM
        result.equivalentFare = AgencyMemoProductType.ACM ? MiscUtil.abs(result.equivalentFare) : MiscUtil.negate(MiscUtil.abs(result.equivalentFare))
        BigDecimal taxes = MiscUtil.sum(prod.agencyMemoProductTaxesCarrierEquivalentAmount, prod.agencyMemoProductTaxesFuelEquivalentAmount,
                prod.agencyMemoProductTaxesStateEquivalentAmount, prod.agencyMemoProductTaxesOthersEquivalentAmount)
        BigDecimal penalty = MiscUtil.sum(prod.agencyMemoProductPenaltyEquivalentAmount, prod.agencyMemoProductContractPenaltyEquivalentAmount)
        result.taxesSum = MiscUtil.sum(taxes, penalty)
        result.taxesSum = AgencyMemoProductType.ACM ? MiscUtil.abs(result.taxesSum) : MiscUtil.negate(MiscUtil.abs(result.taxesSum))
    } else {
        result.status = prod.status
        result.equivalentFare = prod.equivalentFare
        if (ProductStatus.REFUND == prod.status) {
            result.taxesSum = MiscUtil.sum(MiscUtil.negate(prod.taxesSum), prod.penalty)
        } else {
            result.taxesSum = MiscUtil.sum(prod.taxesSum, prod.penalty)
        }
    }
    result.validator = prod.validator
    result.isMCO = prod.mcoCategory ? true : false
    return result
}

// Exchange document
def createWrapperFromDoc = {BSPExchangeDocumentIndex prod ->
    if (prod == null) return
    if (excludeZeroPriceTickets) {
        if (MiscUtil.isZero(prod.equivalentFare, true) &&
                MiscUtil.isZero(prod.taxesSum, true)) {
            return
        }
    }
    BSPWrapper result = new BSPWrapper()
    result.source = "BSP"
    result.conjCount = prod.conjCount ?: 0
    result.conjTickets.addAll(prod.getConjunctions())
    result.ticketNumber = prod.ticketNumber
//    for (String conj : result.conjTickets) {
//        result.ticketNumber = result.ticketNumber+"/"+conj.substring(conj.length() - 1)
//    }
    result.operationDate = prod.operationDate
    result.status = transactionTypeToStatus(prod.transactionType)
    result.equivalentFare = MiscUtil.abs(prod.equivalentFare)
    result.commissionRate = prod.commissionRate
    result.commissionValue = MiscUtil.abs(prod.commissionValue)
    if (TextUtil.equals(prod.getTransactionCode(), "ADMA", false)) {
        result.taxesSum = MiscUtil.negate(MiscUtil.abs(prod.taxesSum))
        result.equivalentFare = MiscUtil.negate(result.equivalentFare)
    } else if (TextUtil.equals(prod.getTransactionCode(), "ACMA", false)) {
        result.taxesSum = MiscUtil.abs(prod.taxesSum)
    } else {
        result.taxesSum = prod.taxesSum
    }
    result.validator = prod.validator
    result.isMCO = prod.isMCO
    return result
}

//creating wrappers from products
def productWrappers = []
allTickets.each {AirTicketsTemplateReportTicket ticket ->
    productWrappers << createWrapperFromProduct(ticket)
}

boolean isParamTrue(String paramName) {
    Boolean param = parameters[paramName]
    return BooleanUtil.nullAsFalse(param)
}

boolean isEqualTicketNumber(String docTicketNumber, BSPWrapper prod) {
    if(TextUtil.isBlank(docTicketNumber) || prod == null || TextUtil.isBlank(prod.ticketNumber)){
        return false
    }
    if(prod.ticketNumber.contains(docTicketNumber)){
        return true
    }
    return prod.conjTickets.contains(docTicketNumber)
}

//creating wrappers from BSP documents
SearchQuery query = new SearchQuery()
query.getCriteria().getCriterions().add(SearchCriterion.ge(BSPExchangeDocumentIndex.Property.operationDate.name(), parameters['params'].periodBegin))
query.getCriteria().getCriterions().add(SearchCriterion.le(BSPExchangeDocumentIndex.Property.operationDate.name(), parameters['params'].periodEnd))
def documentWrappers = []
EntityStorage.get().search(BSPExchangeDocumentIndex.class, query).getData().each { BSPExchangeDocumentIndex idx ->
    documentWrappers << createWrapperFromDoc(idx)
}

def resultWrappers = []
documentWrappers.each { BSPWrapper doc ->
    if (doc == null) return
    BSPWrapper found = productWrappers.find { BSPWrapper prod ->
        if (doc.status == prod.status && isEqualTicketNumber(doc.ticketNumber, prod)) {
            return true
        }
        return false
    }

    if (found == null) {
        resultWrappers << doc
        return
    }

    if (excludeZeroPriceTickets) {
        if (MiscUtil.isZero(doc.equivalentFare, true) &&
                MiscUtil.isZero(doc.taxesSum, true) &&
                MiscUtil.isZero(found.equivalentFare, true) &&
                MiscUtil.isZero(found.taxesSum, true)) {
            return
        }
    }

    productWrappers.remove(found)

    BSPWrapper item1 = new BSPWrapper()
    item1.source = doc.source
    item1.status = doc.status
    item1.ticketNumber = doc.ticketNumber
    BSPWrapper item2 = new BSPWrapper()
    item2.source = found.source
    item2.status = found.status
    item2.ticketNumber = found.ticketNumber
    boolean hasDiff = false
    if (!notCheckCommissions) {
        if (!equalsByCommissionRate(doc, found)) {
            hasDiff = true
            item1.commissionRate = doc.commissionRate
            item2.commissionRate = found.commissionRate
        }
        if (!equalsByCommissionValue(doc, found)) {
            hasDiff = true
            item1.commissionValue = doc.commissionValue
            item2.commissionValue = found.commissionValue
        }
    }
    if (!notCheckIssueDates) {
        if (!equalsByDate(doc, found)) {
            hasDiff = true
            item1.operationDate = doc.operationDate
            item2.operationDate = found.operationDate
        }
    }
    if (!equalsByTariff(doc, found)) {
        hasDiff = true
        item1.equivalentFare = doc.equivalentFare
        item2.equivalentFare = found.equivalentFare
    }
    if (!equalsByTaxesSum(doc, found)) {
        hasDiff = true
        item1.taxesSum = doc.taxesSum
        item2.taxesSum = found.taxesSum
    }
    if (!notCheckValidators) {
        if (!equalsByValidator(doc, found)) {
            hasDiff = true
            item1.validator = doc.validator
            item2.validator = found.validator
        }
    }
    if (hasDiff) {
        resultWrappers << item1
        resultWrappers << item2
    }

}
productWrappers.each { BSPWrapper doc ->
    if (excludeZeroPriceTickets) {
        if (MiscUtil.isZero(doc.equivalentFare, true) &&
                MiscUtil.isZero(doc.taxesSum, true)) {
            return
        }
    }

    resultWrappers << doc
}

resultWrappers.sort{ BSPWrapper it -> it?.ticketNumber + it?.source + it?.status}
allTickets.clear()
allTickets.addAll(resultWrappers)


//REPORT
page{"Сравнение"}{
    // Set portrait mode
    landscape(true)

    // Set narrow margins
    margin(0.25, 0.25, 0.75, 0.75)

    // Set scale
    scale(90)

    //TITLE
    rowHeight(30)
    setStyle('title')
    text("Сравнение данных о продажах за период ${parameters.REPORT_PERIOD}")

    //HEADER
    setStyle('normal')
    nextRow()

    rowHeight(15)
    nextRow()

    rowHeight(40)
    setStyle('header')
    text('Источник'); nextColumn()
    columnWidth(12)
    text('Номер билета'); nextColumn()
    columnWidth(10)
    text('Дата'); nextColumn()
    columnWidth(13)
    text('Статус'); nextColumn()
    columnWidth(12)
    text('Тариф'); nextColumn()
    columnWidth(10)
    text('% комиссии'); nextColumn()
    columnWidth(10)
    text('Сумма комиссии'); nextColumn()
    columnWidth(10)
    text('Таксы и штрафы'); nextColumn()
    columnWidth(10)
    text('Валидатор'); nextColumn()
    nextRow()

    tickets{ BSPWrapper wrapper ->
        rowHeight(12)
        text(wrapper.source,'textData'); nextColumn()
        text(wrapper.ticketNumber,'textData'); nextColumn()
        date(wrapper.operationDate,'dateData'); nextColumn()
        text(wrapper.status?.toString(),'textData'); nextColumn()
        number(wrapper.equivalentFare,'numberData'); nextColumn()
        number(wrapper.commissionRate,'numberData'); nextColumn()
        number(wrapper.commissionValue,'numberData'); nextColumn()
        number(wrapper.taxesSum,'numberData'); nextColumn()
        text(wrapper.validator,'textData'); nextColumn()
        nextRow()
    }

}

class BSPWrapper {

    String airlineCode
    String ticketNumber
    String validator
    Date operationDate
    BigDecimal equivalentFare
    BigDecimal commissionRate
    BigDecimal commissionValue
    BigDecimal total
    BigDecimal totalCash
    BigDecimal totalCredit
    List<Tax> taxes = new ArrayList<>()
    String statCode
    String tourCode
    boolean isMCO
    String previousAirlineCode
    String previousTicketNumber
    String previousCoupons
    List<String> conjTickets = new ArrayList<>()
    String electronicSettlementAuthorisation
    int conjCount = 0
    BigDecimal taxesSum

    String source
    ProductStatus status
}