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

import com.gridnine.xtrip.common.model.booking.ProductIndex
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.booking.air.AirProductContractRelationData
import com.gridnine.xtrip.common.model.booking.air.Product
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.booking.air.Commission
import com.gridnine.xtrip.common.model.booking.commission.BaseCommissionProperties

import groovy.transform.Field

import java.util.List
import java.util.Map

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.commission.BaseCategorizedCommissionProperties
import com.gridnine.xtrip.common.model.dict.CommissionCategory
import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.util.LocaleUtil
import com.gridnine.xtrip.common.model.dict.CommissionCategoryReference

import java.util.Collection

import com.gridnine.xtrip.common.model.booking.air.ProductFop
import com.gridnine.xtrip.common.model.helpers.AirProductHelper
import java.util.Set
import java.math.BigDecimal
import com.gridnine.xtrip.common.vip.CommonVIPHelper
import com.gridnine.xtrip.common.model.booking.vip.VipSubagentSitePriceStructure
import com.gridnine.xtrip.common.model.booking.vip.VipClientSitePriceStructure
import com.google.common.base.Joiner
import com.gridnine.xtrip.common.model.helpers.AirProductTaxHelper
import com.gridnine.xtrip.common.model.helpers.AirProductHelper
import com.gridnine.xtrip.common.model.booking.ProductStatus

class ProductCommission implements Comparable<ProductCommission> {
    Commission commission
    ProductFop fop
    BaseCategorizedCommissionProperties properties
    
    public int compareTo(ProductCommission other) {
        return properties.category.code.compareTo(other.properties.category.code)
    }
}

class ProductStruct {
    Product product
    List<ProductCommission> subagencyCommissions
    List<ProductCommission> clientCommissions
}

class ExchangeProductStruct {
    ProductStruct sell1, exchange, sell2
}

createStyle(name: 'data', h_alignment: 'CENTER', v_alignment: 'BOTTOM', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', 'wrapText' : true)
createStyle(name: 'errorData', parent : 'data', foreground: 'RED')

@Field CommissionCategoryReference rviCommissionCategoryRef = getCommissionCategoryRef('RVI')
@Field CommissionCategoryReference yviCommissionCategoryRef = getCommissionCategoryRef('YVI')

CommissionCategoryReference getCommissionCategoryRef(String name) {
    Collection<CommissionCategory> commissionCategories = DictionaryCache.get().getAll(CommissionCategory.class).values()
    for (CommissionCategory category :commissionCategories) {
        if (category.toString(LocaleUtil.LOCALE_EN) == name) {
            return new CommissionCategoryReference(category)
        }
    }
}

@Field Map<EntityReference, EntityContainer<BaseCommissionProperties>> commissionPropertiesCntCache = [:]
EntityContainer<BaseCommissionProperties> getCommissionPropertiesCnt(EntityReference commissionPropertiesRef) {
    if (commissionPropertiesCntCache.containsKey(commissionPropertiesRef)) {
        return commissionPropertiesCntCache[commissionPropertiesRef]
    }
    EntityContainer<BaseCommissionProperties> commissionPropertiesCnt = EntityStorage.get().resolve(commissionPropertiesRef)
    commissionPropertiesCntCache[commissionPropertiesRef] = commissionPropertiesCnt
    return commissionPropertiesCnt
}

List<ProductCommission> getProductCommissions(AirProductContractRelationData contractRelation, List<ProductFop> fops) {
    List<Commission> commissions = []
    if (contractRelation.commissions != null) {
        for (Commission commission : contractRelation.commissions) {
            BaseCommissionProperties commissionProperties = getCommissionPropertiesCnt(commission.commissionProperties)?.entity
            String type
            if (commissionProperties instanceof BaseCategorizedCommissionProperties) {
                BaseCategorizedCommissionProperties categorizedCommissionProperties = commissionProperties
                if (categorizedCommissionProperties.category == rviCommissionCategoryRef || categorizedCommissionProperties.category == yviCommissionCategoryRef) {
                    commissions.add(commission)
                }
            }
        }
    }
    if (commissions.empty) {
        return []
    }
    List<ProductCommission> productCommissions = []
    for (Commission commission : commissions) {
        ProductFop fop = null
        for (ProductFop ifop : contractRelation.fops) {
            if (ifop.commissions.contains(commission)) {
                fop = ifop
                break
            }
        }
        ProductCommission productCommission = new ProductCommission()
        productCommission.commission = commission
        productCommission.fop = fop
        productCommission.properties = getCommissionPropertiesCnt(commission.commissionProperties)?.entity
        productCommissions.add(productCommission)
    }
    Collections.sort(productCommissions)
    return productCommissions
}

ProductStruct getProductStruct(Product product) {
    AirProductContractRelationData subagencyRelation = AirProductHelper.getSubagentContractRelation(product, true)
    List<ProductFop> subagencyFops = AirProductHelper.getSubagentFops(product, true)
    List<ProductCommission> subagencyCommissions = getProductCommissions(subagencyRelation, subagencyFops)
    AirProductContractRelationData clientRelation = AirProductHelper.getClientContractRelation(product)
    List<ProductFop> clientFops = AirProductHelper.getClientFops(product)
    List<ProductCommission> clientCommissions = getProductCommissions(clientRelation, clientFops)
    ProductStruct result = new ProductStruct()
    result.product = product
    result.subagencyCommissions = subagencyCommissions
    result.clientCommissions = clientCommissions
    return result
}

ExchangeProductStruct getExchangeProductStruct(ProductIndex productIndex) {
    BookingFile booking = EntityStorage.get().resolve(productIndex.source)?.entity
    if (booking == null) {
        warn ("booking for index ${productIndex} not found")
        return null
    }
    Product exchangeProduct = BookingHelper.findProductByUid(productIndex.navigationKey, booking)
    if (exchangeProduct == null) {
        warn("product with uid ${productIndex.navigationKey} not found in booking")
        return null
    }
    ProductStruct exchange = getProductStruct(exchangeProduct)
    if (exchange.subagencyCommissions.empty && exchange.clientCommissions.empty) {
        return null
    }
    Product sell1Product = exchangeProduct.previousProduct
    ProductStruct sell1
    if (sell1Product != null) {
        sell1 = getProductStruct(sell1Product)
    } else {
        sell1 = null
    }
    Product sell2Product = exchangeProduct.nextProduct
    ProductStruct sell2
    if (sell2Product != null) {
        sell2 = getProductStruct(sell2Product)
    } else {
        sell2 = null
    }
    ExchangeProductStruct result = new ExchangeProductStruct()
    result.sell1 = sell1
    result.sell2 = sell2
    result.exchange = exchange
    return result
}

@Field double eps = 1e-3
boolean isEquals(BigDecimal val1, BigDecimal val2) {
    if (val1 == null || val2 == null) {
        return false
    }
    double diff = val1.subtract(val2).abs().doubleValue()
    return diff < eps
}

@Field String DIFFERENT_COMMISSIONS = 'не найден скрытый сбор для сравнения'
@Field String DIFFERENT_FOP_AMOUNTS = 'различные значения фопов'
@Field String DIFFERENT_COMMISSIONS_AMOUNT = 'различные значения скрытых сборов'
@Field String DIFFERENT_SUBAGENCY_FOP_AMOUND = 'субагентский фоп не равен (тариф + такса + скрытый сбор)'
@Field String DIFFERENT_CLIENT_FOP_AMOUND = 'клиентский фоп не равен (тариф + такса + скрытый сбор)'
@Field String DIFFERENT_EXCHANGE_COMMISSION = 'скрытый сбор в обмене не равен скрытому сбору в продаже'
@Field String DIFFERENT_EXCHANGE_FOP = 'фоп в обмене не равен фопу в продаже'

Set<String> checkProductStruct(ProductStruct productStruct) {
    Set<String> result = [] as Set
    for (int i = 0; i < productStruct.subagencyCommissions.size(); i++) {
        ProductCommission subagencyCommission = productStruct.subagencyCommissions.get(i)
        ProductCommission clientCommission = productStruct.clientCommissions.get(i)
        if (!isEquals(subagencyCommission.fop?.amount?.value, clientCommission.fop?.amount?.value)) {
            warn("product_number: ${productStruct.product.systemNumber}, subagent_fop: ${subagencyCommission.fop?.amount?.value}, client_fop: ${clientCommission.fop?.amount?.value}")
            result.add(DIFFERENT_FOP_AMOUNTS)
        }
        if (!isEquals(subagencyCommission.commission?.equivalentAmount, clientCommission.commission?.equivalentAmount)) {
            result.add(DIFFERENT_COMMISSIONS_AMOUNT)
        }
        BigDecimal tariff = AirProductHelper.getEquivalentFare(productStruct.product)
        BigDecimal taxes = AirProductTaxHelper.getEquivalentTaxesAmount(productStruct.product)
        BigDecimal commissionVal = subagencyCommission.commission?.equivalentAmount
        if (commissionVal == null) {
            commissionVal = BigDecimal.ZERO
        }
        BigDecimal fopVal = subagencyCommission.fop?.amount?.value
        if (fopVal == null) {
            fopVal = BigDecimal.ZERO
        }
        if (!isEquals(tariff.add(taxes).add(commissionVal), fopVal)) {
            warn("tarif: ${tariff}, tax: ${taxes}, com: ${commissionVal}, fop: ${fopVal}")
            result.add(DIFFERENT_SUBAGENCY_FOP_AMOUND)
        }
        commissionVal = clientCommission.commission?.equivalentAmount
        if (commissionVal == null) {
            commissionVal = BigDecimal.ZERO
        }
        fopVal = clientCommission.fop?.amount?.value
        if (fopVal == null) {
            fopVal = BigDecimal.ZERO
        }
        if (!isEquals(tariff.add(taxes).add(commissionVal), fopVal)) {
            warn("tarif: ${tariff}, tax: ${taxes}, com: ${commissionVal}, fop: ${fopVal}")
            result.add(DIFFERENT_CLIENT_FOP_AMOUND)
        }
    }
    return result
}

Set<String> checkExchangeProductStruct(ExchangeProductStruct exchangeStruct) {
    Set<String> result = [] as Set
    if (exchangeStruct.sell1.subagencyCommissions.size() != exchangeStruct.sell1.clientCommissions.size() ||
            exchangeStruct.sell1.subagencyCommissions.size() != exchangeStruct.exchange.subagencyCommissions.size() ||
            exchangeStruct.sell1.subagencyCommissions.size() != exchangeStruct.exchange.clientCommissions.size() ||
            exchangeStruct.sell1.subagencyCommissions.size() != exchangeStruct.sell2.subagencyCommissions.size() ||
            exchangeStruct.sell1.subagencyCommissions.size() != exchangeStruct.sell2.clientCommissions.size()) {
        result.add(DIFFERENT_COMMISSIONS)
        return result
    }
    for (int i = 0; i < exchangeStruct.sell1.subagencyCommissions.size(); ++i) {
        String commissionCategoryCode = exchangeStruct.sell1.subagencyCommissions.get(i).properties.category.code
        if (commissionCategoryCode != exchangeStruct.sell1.clientCommissions.get(i).properties.category.code ||
                commissionCategoryCode != exchangeStruct.exchange.subagencyCommissions.get(i).properties.category.code ||
                commissionCategoryCode != exchangeStruct.exchange.clientCommissions.get(i).properties.category.code ||
                commissionCategoryCode != exchangeStruct.sell2.subagencyCommissions.get(i).properties.category.code ||
                commissionCategoryCode != exchangeStruct.sell2.clientCommissions.get(i).properties.category.code) {
            result.add(DIFFERENT_COMMISSIONS)
            return result
        }
    }
    result.addAll(checkProductStruct(exchangeStruct.sell1))
    result.addAll(checkProductStruct(exchangeStruct.exchange))
    result.addAll(checkProductStruct(exchangeStruct.sell2))
    for (int i = 0; i < exchangeStruct.sell1.subagencyCommissions.size(); ++i) {
        BigDecimal sell1CommissionAmount = exchangeStruct.sell1.subagencyCommissions.get(i).commission.equivalentAmount
        BigDecimal exchangeCommissionAmount = exchangeStruct.exchange.subagencyCommissions.get(i).commission.equivalentAmount
        BigDecimal sell2CommissionAmount = exchangeStruct.sell2.subagencyCommissions.get(i).commission.equivalentAmount
        if (!isEquals(sell1CommissionAmount, exchangeCommissionAmount)) {
            result.add(DIFFERENT_EXCHANGE_COMMISSION)
        }
        if (!isEquals(sell1CommissionAmount, sell2CommissionAmount)) {
            result.add(DIFFERENT_EXCHANGE_COMMISSION)
        }
        
        sell1CommissionAmount = exchangeStruct.sell1.clientCommissions.get(i).commission.equivalentAmount
        exchangeCommissionAmount = exchangeStruct.exchange.clientCommissions.get(i).commission.equivalentAmount
        sell2CommissionAmount = exchangeStruct.sell2.clientCommissions.get(i).commission.equivalentAmount
        if (!isEquals(sell1CommissionAmount, exchangeCommissionAmount)) {
            result.add(DIFFERENT_EXCHANGE_COMMISSION)
        }
        if (!isEquals(sell1CommissionAmount, sell2CommissionAmount)) {
            result.add(DIFFERENT_EXCHANGE_COMMISSION)
        }
        
        
        BigDecimal sell1FopAmount = exchangeStruct.sell1.subagencyCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        BigDecimal exchangeFopAmount = exchangeStruct.exchange.subagencyCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        BigDecimal sell2FopAmount = exchangeStruct.sell2.subagencyCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        if (!isEquals(sell1FopAmount, exchangeFopAmount) || !isEquals(sell2FopAmount, exchangeFopAmount)) {
            result.add(DIFFERENT_EXCHANGE_FOP)
        }
        sell1FopAmount = exchangeStruct.sell1.clientCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        exchangeFopAmount = exchangeStruct.exchange.clientCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        sell2FopAmount = exchangeStruct.sell2.clientCommissions.get(i).fop?.amount?.value ?: BigDecimal.ZERO
        if (!isEquals(sell1FopAmount, exchangeFopAmount) || !isEquals(sell2FopAmount, exchangeFopAmount)) {
            result.add(DIFFERENT_EXCHANGE_FOP)
        }
    }
    return result
}

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

def printProduct(ProductStruct productStruct, boolean isError) {
    String style
    if (isError) {
        style = 'errorData'
    } else {
        style = 'data'
    }
    text(productStruct.product.systemNumber, style)
    nextColumn()
    
    text(productStruct.product.status.toString(), style)
    nextColumn()
    
    List<String> rviValues = []
    List<String> yviValues = []
    for (ProductCommission commissionStruct : productStruct.subagencyCommissions) {
        BigDecimal value = commissionStruct.commission.equivalentAmount
        if (productStruct.product.status == ProductStatus.EXCHANGE) {
            value = value.negate()
        }
        if (commissionStruct.properties.category == rviCommissionCategoryRef) {
            rviValues.add(value.toString())
        } else if (commissionStruct.properties.category == yviCommissionCategoryRef) {
            yviValues.add(value.toString())
        }
    }
    
    text(commaJoiner.join(rviValues), style)
    nextColumn()
    
    text(commaJoiner.join(yviValues), style)
    
    rowAutoHeight()
    nextRow()
}

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

switchPage('Sheet1')
moveTo(1, 0)
tickets { ProductIndex productIndex ->
    ExchangeProductStruct exchangeStruct = getExchangeProductStruct(productIndex)
    if (exchangeStruct == null) {
        return
    }
    Set<String> checkErrors = checkExchangeProductStruct(exchangeStruct)
    boolean isError = !checkErrors.empty
    int startRow = getRowIndex()
    printProduct(exchangeStruct.sell1, isError)
    printProduct(exchangeStruct.exchange, isError)
    printProduct(exchangeStruct.sell2, isError)
    if (isError) {
        int currentRow = getRowIndex()
        int rowCount = currentRow - startRow
        moveTo(startRow, 4)
        text(commaJoiner.join(checkErrors), 'errorData', 1, rowCount)
        rowAutoHeight()
        moveTo(currentRow, 0)
    }
}