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

import groovy.lang.Closure;
import groovy.transform.Field;

import com.gridnine.xtrip.common.Environment;
import com.gridnine.xtrip.common.l10n.model.LocaleHelper;
import com.gridnine.xtrip.common.model.BaseEntity;
import com.gridnine.xtrip.common.model.EntityContainer;
import com.gridnine.xtrip.common.model.EntityReference;
import com.gridnine.xtrip.common.model.entity.EntityStorage;
import com.gridnine.xtrip.common.model.helpers.ProfileHelper;
import com.gridnine.xtrip.common.model.profile.BaseProfile;
import com.gridnine.xtrip.common.model.profile.Organization;
import com.gridnine.xtrip.common.model.system.BillingTransaction;
import com.gridnine.xtrip.common.model.system.BillingTransactionCategory;
import com.gridnine.xtrip.common.model.system.BillingTransactionIndex;
import com.gridnine.xtrip.common.model.system.BillingTransactionType;
import com.gridnine.xtrip.common.model.system.PaymentType;
import com.gridnine.xtrip.common.search.SearchCriterion;
import com.gridnine.xtrip.common.search.SearchQuery;
import com.gridnine.xtrip.common.util.MiscUtil;
import com.gridnine.xtrip.common.vip.reports.AgencyFilter;
import com.gridnine.xtrip.server.vip.VipReportServerHelper;
import com.gridnine.xtrip.common.model.entity.parameters.EntityStorageActualizeParameters
import java.math.RoundingMode

prepareDate()
createReport()

class IncomeBalanceInfo{
    String      subName;
    String      subInn;
    String      subKpp;
    String      subType;
    String      subDescription;
    Date        subDate;
    BigDecimal  subSumm;
    BigDecimal  paymentFee;
    BigDecimal  paymentFeePercent;
    String orderNumber = '';
}

@Field private List<IncomeBalanceInfo> listInfo = new LinkedList<IncomeBalanceInfo>()

@Field private BigDecimal allSumm           = BigDecimal.ZERO
@Field private BigDecimal allSummBilling    = BigDecimal.ZERO
@Field private BigDecimal allSummCommission = BigDecimal.ZERO
@Field private BigDecimal allSummCorrection = BigDecimal.ZERO

private BillingTransactionType getCompletionType(){
    return parameters['COMPLETION_TYPE']
}
private PaymentType getPaymentType(){
    return parameters['PAYMENT_TYPE']
}
private boolean getAggregation(){
    return parameters['AGGREGATION']
}
private EntityReference<Organization> getAgency(){
    return parameters['AGENCY']
}
private List<EntityReference<Organization>> getSubagency(){
    return parameters['SUBAGENT']
}
private Date getPeriodBegin(){
    return parameters?.params?.periodBegin
}
private Date getPeriodEnd(){
    return parameters?.params?.periodEnd
}

private List<BillingTransactionIndex> getListFromDB(){
    try{
        SearchCriterion debit = SearchCriterion.or(SearchCriterion.and(SearchCriterion.eq(BillingTransactionIndex.Property.category.name(),BillingTransactionCategory.DEBIT), SearchCriterion.gt(BillingTransactionIndex.Property.transactionSum.name(),BigDecimal.valueOf(0))), SearchCriterion.and(SearchCriterion.eq(BillingTransactionIndex.Property.category.name(),BillingTransactionCategory.CREDIT), SearchCriterion.lt(BillingTransactionIndex.Property.transactionSum.name(),BigDecimal.valueOf(0))));

        List<SearchCriterion> crits = new LinkedList<SearchCriterion>();
        BillingTransactionType billingType = getCompletionType();
        boolean allBillingTypes = billingType == null;
        SearchQuery query = new SearchQuery();

        if (allBillingTypes || (billingType == BillingTransactionType.BILLING)) {
            crits.add(SearchCriterion.eq(BillingTransactionIndex.Property.type.name(),BillingTransactionType.BILLING));
        }
        if (allBillingTypes || (billingType == BillingTransactionType.COMMISSION)) {
            crits.add(SearchCriterion.and(SearchCriterion.eq(BillingTransactionIndex.Property.type.name(),BillingTransactionType.COMMISSION), debit));
        }
        if (allBillingTypes || (billingType == BillingTransactionType.CORRECTION)) {
            crits.add(SearchCriterion.and(SearchCriterion.eq(BillingTransactionIndex.Property.type.name(),BillingTransactionType.CORRECTION), debit));
        }

        SearchCriterion[] cr = new SearchCriterion[crits.size()];
        for (int i = 0; i < cr.length; i++) {
            cr[i] = crits.get(i);
        }

        if(getAgency() != null && !getAgency().equals('null')){
            query.getCriteria().getCriterions().add(SearchCriterion.eq(BillingTransactionIndex.Property.agency.name(), getAgency()));
        }

        if (getPaymentType() != null) {
            query.getCriteria().getCriterions().add(SearchCriterion.eq(BillingTransactionIndex.Property.paymentType.name(),getPaymentType()));
        }

        query.getCriteria().getCriterions().add(SearchCriterion.or(cr));

        List<EntityReference<Organization>> subs = getSubagency()

        if (subs.size() > 0) {
            Set<EntityReference<Organization>> subagents = new HashSet<EntityReference<Organization>>(subs);

            SearchCriterion[] criterions = new SearchCriterion[subagents.size()];
            int index = 0;
            for (EntityReference<Organization> ref : subagents) {
                criterions[index] = (SearchCriterion.eq(BillingTransactionIndex.Property.client.name(), ref));
                index++;
            }
            query.getCriteria().getCriterions().add(SearchCriterion.or(criterions));
        }
        query.getCriteria().getCriterions().add(SearchCriterion.and(SearchCriterion.ge(BillingTransactionIndex.Property.transactionDate.name(),getPeriodBegin()), SearchCriterion.le(BillingTransactionIndex.Property.transactionDate.name(),getPeriodEnd())));
        query.criteria.criterions.add(SearchCriterion.ne(BillingTransactionIndex.Property.client.name(), ProfileHelper.getRetailProfileContainer().toReference()))
        List result = EntityStorage.get().search(BillingTransactionIndex.class, query).data
        info ("result.size: ${result.size()}")
        return result;
    }catch(Exception e){
        warn e?.getMessage()
        return Collections.EMPTY_LIST
    }
}

private void createMainList(List<BillingTransactionIndex> list){
    Map<EntityReference<? extends BaseEntity>, IncomeBalanceInfo> referencedEntityToRowMap = new HashMap<EntityReference<? extends BaseEntity>, IncomeBalanceInfo>();
    EntityStorage entityCache = Environment.getPublished(EntityStorage.class);

    list.each{BillingTransactionIndex index ->
        IncomeBalanceInfo reportRow;
        boolean addToList = true;

        if (getAggregation() && (index.getReferencedEntity() != null) && (index.getCategory() == BillingTransactionCategory.DEBIT)) {
            if (!referencedEntityToRowMap.containsKey(index.getReferencedEntity())) {
                reportRow = new IncomeBalanceInfo();
                referencedEntityToRowMap.put(index.getReferencedEntity(),reportRow);
            } else {
                reportRow =referencedEntityToRowMap.get(index.getReferencedEntity());
                addToList = false;
            }
        } else {
            reportRow = new IncomeBalanceInfo();
        }

        if (reportRow.subDate == null) {
            reportRow.subDate = index.getTransactionDate();
        }
        
        EntityStorageActualizeParameters actualizeParameters = new EntityStorageActualizeParameters();
        actualizeParameters.useRemoteCallIfNecessary = true;

        if (reportRow.subDescription == null) {
            if (referencedEntityToRowMap.containsKey(index.getReferencedEntity())) {
                entityCache.actualize(index.getReferencedEntity(), actualizeParameters);
                reportRow.subDescription = String.format("%s (%s)", index.getDescription(), index.getReferencedEntity().getCaption());
            } else {
                reportRow.subDescription = index.getDescription();
            }
        }

        if (reportRow.subType == null) {
            reportRow.subType = index.getType().toString();
        }

        if (index.getClient() == null) {
            return;
        }

        entityCache.actualize(index.getClient(), actualizeParameters);
        EntityContainer<? extends BaseProfile> profileCtr = entityCache.resolve(index.getClient());

        BaseProfile profile = profileCtr != null ? profileCtr.getEntity() : null;
        if (profile instanceof Organization) {
            Organization client = (Organization) profile;
            reportRow.subKpp = client.getKpp();
            reportRow.subInn = client.getRegistrationId();
        }

        if (index.getClient().getCaption() != null) {
            reportRow.subName = index.getClient().getCaption();
        }

        if (index.getTransactionSum() != null) {
            reportRow.subSumm = index.getTransactionSum().abs().add(reportRow.subSumm == null ? BigDecimal.ZERO : reportRow.subSumm);
            
            if (index.getPaymentType() == PaymentType.CREDIT_CARD_ONLINE_PAYTURE) {

                // add card vendor and payment fee
                final BillingTransactionIndex feeTransaction =
                    getRelatedChildTransaction(index);
                if (feeTransaction != null) {
                    reportRow.paymentFee = feeTransaction.getTransactionSum();
                    if (reportRow.paymentFee != null) {
                    	reportRow.paymentFeePercent = reportRow.paymentFee.multiply(BigDecimal.valueOf(100d)).divide(reportRow.subSumm, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);  
                    }
                }
            }
        }

        String orderNumber = '';
        BillingTransaction bt = entityCache.resolve(index.getSource()).getEntity();
        if(reportRow.getOrderNumber().isEmpty()){
            orderNumber = bt.getEntity().toString();
        } else {
            orderNumber = reportRow.getOrderNumber() + ', ' + bt.getEntity().toString();
        }
        orderNumber = orderNumber == null || orderNumber.equals('null') ? '' : orderNumber;
        reportRow.setOrderNumber(orderNumber);

        if (addToList) {
            this.listInfo.add(reportRow);
        }
    }

    Collections.sort(this.listInfo, new Comparator<IncomeBalanceInfo>(){
                @Override
                public int compare(final IncomeBalanceInfo o1,final IncomeBalanceInfo o2) {
                    int cmp = o1.subName.compareTo(o2.subName);

                    if (cmp == 0) {
                        cmp = o1.subType.compareTo(o2.subType);
                    }

                    if (cmp == 0) {
                        cmp = o1.subDate.compareTo(o2.subDate);
                    }
                    return cmp;
                }
            });
}

private void prepareDate(){
    createMainList(getListFromDB())
}

private void createReport(){
    reportSheetStyle()
    page{'VIP-INCOME'}{
        reportHead()
        reportBody()
    }
}

private void reportHead(){
    def subagencyNames = {
        EntityStorage entityCache =
                Environment.getPublished(EntityStorage.class);
        StringBuilder sb = new StringBuilder();
        for (EntityReference<Organization> ref : getSubagency()) {
            EntityContainer<Organization> ctr = entityCache.resolve(ref);
            sb.append(ProfileHelper.getFullName(ctr.getEntity(),
                    LocaleHelper.getCurrentLocale(), false));
            sb.append(", ");
        }
        if (sb.length() > 0) {
            sb.delete(sb.lastIndexOf(","), sb.length());
        } else {
            sb.append("не указан");
        }
        return sb.toString()
    }

    int scw = 10
    rowHeight(3*scw)
    text('Отчет по внесенным платежам','header3')
    nextRow()
    rowHeight(1.3*scw)
    nextRow()
    text('Дата составления отчета:','header4')
    nextColumn();text(parameters.REPORT_DATE,'metadataValue')
    nextRow()
    text('Период:','header4')
    nextColumn();text(parameters.REPORT_PERIOD, 'metadataValue')
    nextRow()
    text('Субагент:','header4')
    nextColumn();text(subagencyNames(), 'metadataValue')

    2.times{nextRow()}

    setStyle('header')
    rowHeight(3*scw)
    columnWidth(2*scw);text('Номер заказа','m1');
    nextColumn();columnWidth(4*scw);text('Наименование субагента','m1');
    nextColumn();columnWidth(1.5*scw);text('ИНН:','m1');
    nextColumn();columnWidth(1.5*scw);text('КПП:','m1');
    nextColumn();columnWidth(1.5*scw);text('Тип:','m1');
    nextColumn();columnWidth(2*scw);text('Описание:','m1');
    nextColumn();columnWidth(1.5*scw);text('Дата:','m1');
    nextColumn();columnWidth(1.5*scw);text('Cумма списание за транзакцию:','m1');
    nextColumn();columnWidth(1.5*scw);text('% списание за транзакцию:','m1');
    nextColumn();columnWidth(2*scw);text('Сумма:','m1');
}

private void reportBody(){
    body()
    end()
}

private void body(){
    int scw = 10

    def subName = 0
    def subNameSumm = BigDecimal.ZERO

    def subType = 0
    def subTypeSumm = BigDecimal.ZERO

    this.listInfo.each{IncomeBalanceInfo info ->
        if(!subType.equals(info.subType) || !subName.equals(info.subName)){
            if(!subType.equals(0)){
                processNextCell(1, 1) {rowHeight(1.3*scw);text('ИТОГО','textDataBold')}
                processNextCell(0, 6) {number(subTypeSumm,'numberDataBold')}

                subNameSumm = MiscUtil.sum(subNameSumm,subTypeSumm)
                subTypeSumm = BigDecimal.ZERO
            }
            subType = info.subType;
        }
        if(!subName.equals(info.subName)){
            if(!subName.equals(0)){
                processNextCell(1, 0) {rowHeight(1.3*scw);text('ИТОГО','textDataBold')}
                processNextCell(0, 7) {number(subNameSumm,'numberDataBold')}

                //                this.allSumm = MiscUtil.sum(subNameSumm,this.allSumm)
                subNameSumm = BigDecimal.ZERO
            }
            subName = info.subName;
        }

        processNextCell(1, 0) {rowHeight(1.3*scw);text(info.getOrderNumber(),'textData')}
        processNextCell(0, 1) {text(subName,'textData')}
        processNextCell(0, 1) {text(info.subInn,'textData')}
        processNextCell(0, 1) {text(info.subKpp,'textData')}
        processNextCell(0, 1) {text(subType,'textData')}
        processNextCell(0, 1) {text(info.subDescription,'textData')}
        processNextCell(0, 1) {date(info.subDate,'dateData')}
        processNextCell(0, 1) {number(info.paymentFee,'numberData')}
        processNextCell(0, 1) {number(info.paymentFeePercent,'numberData')}
        processNextCell(0, 1) {number(info.subSumm,'numberData')}
        
        subTypeSumm = MiscUtil.sum(info.subSumm,subTypeSumm)
        this.allSumm = MiscUtil.sum(info.subSumm,this.allSumm)

        if(info.subType.equals(BillingTransactionType.BILLING.toString())){
            this.allSummBilling = MiscUtil.sum(this.allSummBilling,info.subSumm)
        } else if (info.subType.equals(BillingTransactionType.COMMISSION.toString())){
            this.allSummCommission = MiscUtil.sum(this.allSummCommission,info.subSumm)
        } else if (info.subType.equals(BillingTransactionType.CORRECTION.toString())){
            this.allSummCorrection = MiscUtil.sum(this.allSummCorrection,info.subSumm)
        }
    }
    if(this.listInfo.size() > 0){
        //        концовка
        processNextCell(1, 1) {rowHeight(1.3*scw);text('ИТОГО','textDataBold')}
        processNextCell(0, 8) {number(subTypeSumm,'numberDataBold')}

        subNameSumm = MiscUtil.sum(subNameSumm,subTypeSumm)

        processNextCell(1, 0) {rowHeight(1.3*scw);text('ИТОГО','textDataBold')}
        processNextCell(0, 9) {number(subNameSumm,'numberDataBold')}
    }
}

private void end(){
    int scw = 10
    processNextCell(2, 0) {rowHeight(1.3*scw);text('итого пополнения','textDataBold')}
    processNextCell(0, 9) {number(this.allSummBilling,'numberDataBold')}

    processNextCell(1, 0) {rowHeight(1.3*scw);text('итого по всем вознаграждениям','textDataBold')}
    processNextCell(0, 9) {number(this.allSummCommission,'numberDataBold')}

    processNextCell(1, 0) {rowHeight(1.3*scw);text('итого по всем коррекциям','textDataBold')}
    processNextCell(0, 9) {number(this.allSummCorrection,'numberDataBold')}

    processNextCell(1, 0) {rowHeight(1.3*scw);text('ИТОГО ПО ВСЕМ','textDataBold')}
    processNextCell(0, 9) {number(this.allSumm,'numberDataBold')}
}

private void reportSheetStyle(){
    createStyle(name: 'title',fontBold: true, h_span: 14, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:20)
    createStyle(name: 'metadataTitle',fontBold: true, h_span: 3, h_alignment: 'RIGHT', v_alignment: 'CENTER', fontHeight:10)
    createStyle(name: 'metadataValue', parent: 'metadataTitle', h_span: 10, fontBold: false, h_alignment: 'LEFT')
    createStyle(name: 'metadataDateValue', parent: 'metadataValue', format: 'm/d/yy')
    createStyle(name: 'topHeader', h_span: 5, fontBold: true,  h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
    createStyle(name: 'header',fontBold: true,  h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
    createStyle(name: 'header2', fontBold: true,  fontHeight:10, leftBorder:'THIN', rightBorder:'THIN', wrapText: true)
    createStyle(name: 'data',fontBold: false,  h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:8, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN')
    createStyle(name: 'textData',parent: 'data')
    createStyle(name: 'textDataBold',parent: 'data', fontBold: true)
    createStyle(name: 'numberData', h_alignment: 'RIGHT', format: '#,##0.00', parent: 'data')
    createStyle(name: 'numberDataBold', h_alignment: 'RIGHT', format: '#,##0.00', parent: 'data',fontBold: true)
    createStyle(name: 'numberDataInt', h_alignment: 'RIGHT', parent: 'data')
    createStyle(name: 'dateData', parent: 'data', h_alignment: 'RIGHT', format: 'm/d/yy')
    createStyle(name: 'timeData', parent: 'data', h_alignment: 'RIGHT', format: 'h:mm')
    createStyle(name: 'spaceData', parent: 'data', topBorder:'NONE', bottomBorder:'NONE')
    createStyle(name: 'totalText', parent: 'header')
    createStyle(name: 'totalNumber', parent: 'header', h_alignment: 'RIGHT')

    createStyle(name: 'header3', fontBold: true,  fontHeight:16, leftBorder:'THIN', rightBorder:'THIN', wrapText: true, h_span: 5)
    createStyle(name: 'header4', fontBold: true,  fontHeight:10, leftBorder:'THIN', rightBorder:'THIN', wrapText: true, h_span: 2)

    createStyle(name: 'm0',fontBold: true, h_span: 1, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10)
    createStyle(name: 'm1',fontBold: true, h_span: 1, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm2',fontBold: true, h_span: 2, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm3',fontBold: true, h_span: 3, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm4',fontBold: true, h_span: 4, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm5',fontBold: true, h_span: 5, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm6',fontBold: true, h_span: 6, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm7',fontBold: true, h_span: 7, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm8',fontBold: true, h_span: 8, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm9',fontBold: true, h_span: 9, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:10, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
    createStyle(name: 'm11',fontBold: true, h_span: 1, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight:8, leftBorder:'MEDIUM', rightBorder:'MEDIUM', topBorder:'MEDIUM', bottomBorder:'MEDIUM', wrapText: true)
}

private void processNextCell(int numberOfRowShifts, int numberOfColumnShifts,
        Closure action) {
    numberOfRowShifts.times { nextRow() }
    numberOfColumnShifts.times { nextColumn() }
    action()
}


private BillingTransactionIndex getRelatedChildTransaction(
        final BillingTransactionIndex parent) {

    SearchQuery query = new SearchQuery();
    query
        .getCriteria()
        .getCriterions()
        .add(
            SearchCriterion.and(
                SearchCriterion.eq(
                    BillingTransactionIndex.Property.transactionDate.name(),
                    parent.getTransactionDate()),
                SearchCriterion.and(
                    SearchCriterion.eq(
                        BillingTransactionIndex.Property.parent.name(),
                        parent.getSource()))));

    List<BillingTransactionIndex> indexes =
        EntityStorage.get().search(BillingTransactionIndex.class, query)
            .getData();

    if (indexes.size() > 0) {
        return indexes.get(0);
    }
    return null;
}

warn 'reportVersion=' + '0.1.1'