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

import java.util.concurrent.TimeUnit;

import com.gridnine.xtrip.common.Environment;
import com.gridnine.xtrip.common.correspondence.IssueIndex;
import com.gridnine.xtrip.common.correspondence.IssueStatus;
import com.gridnine.xtrip.common.correspondence.IssueType;
import com.gridnine.xtrip.common.model.dict.DictionaryCache;
import com.gridnine.xtrip.common.model.entity.EntityStorage;
import com.gridnine.xtrip.common.model.helpers.ProfileHelper;
import com.gridnine.xtrip.common.util.MiscUtil;
import com.gridnine.xtrip.common.util.TextUtil;
import com.gridnine.xtrip.server.storage.DictionaryStorage;
import com.gridnine.xtrip.common.vip.ProfileHelper
import groovy.transform.Field;
import com.gridnine.xtrip.common.vip.SystemProfile
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.profile.Person
import com.gridnine.xtrip.common.correspondence.BaseIssue
import com.gridnine.xtrip.common.model.EntityContainer
import java.util.Date
import com.gridnine.xtrip.common.correspondence.Post
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.correspondence.IssueStatus

prepareDate()
createReport()

@Field private Map<String,Object> mainMap = new HashMap<String,Object>();
@Field private IssueStatistics statistics = new IssueStatistics();
@Field private Map<StatType,Object> mainStat = new HashMap<StatType,Object>();
@Field EntityReference<Person> systemProfileRef = ProfileHelper.getSystemProfile(SystemProfile.SYSTEM)
@Field BigDecimal eps = new BigDecimal(1e-2)

public enum StatType{
    ORDER,NOT_ORDER,CORR_ALL,CORR_REF_EXC,CORR_REQ,QUESTION_ALL,ANSWER_ALL
}

class IssueInfo{
    String agencyName;
    String subagencyName;
    String numberIssue;
    String numberBooking;
    String queue;
    String type;
    String priority;
    String author;
    String responsible;
    Integer amountResponse;
    Integer amountQuestion;
    BigDecimal averageTimeIssue;
    Date createTime;
    Date createDate;
    Date lastCloseTime;
    Date lastCloseDate;
    BigDecimal activityTime;
}

class IssueStatistics{
    int commonsCount = 0;
    int refundsCount = 0;
    int simpleCount = 0;

    int bookingNumbers = 0;
}

private String getAgencyName(IssueIndex index){
    return EntityStorage.get().resolve(index.agency)?.entity?.shortName
}

private String getSubagencyName(IssueIndex index){
    return EntityStorage.get().resolve(index.getSubagency())?.entity?.shortName
}

private String getNumberIssue(IssueIndex index){
    return index.getNumber()
}

private String getNumberBooking(IssueIndex index){
    return index.getBookingNumber()
}

private String getQueue(IssueIndex index){
    def queue = Environment.getPublished(DictionaryCache.class).resolveReference(index.getTarget()).toString()
    return queue == null || queue.equals('null')? '' : queue
}

private String getType(IssueIndex index){
    return index.getType().toString()
}

private String getPriority(IssueIndex index){
    return index.getPriority().toString()
}

private String getAuthor(IssueIndex index){
    def author = EntityStorage.get().resolve(index.client)?.entity.toString()
    return author == null || author.equals('null')? '' : author
}

private String getResponsible(IssueIndex index){
    def res = EntityStorage.get().resolve(index.assignee)?.entity.toString()
    return res == null || res.equals('null')? '' : res
}

private Integer getAmountResponse (IssueIndex index){
    int responsesCount =
            index.getResponsesCount() != null ? index.getResponsesCount()
            .intValue() : 0;

    return responsesCount
}

private Integer getAmountQuestion(IssueIndex index){
    int responsesCount =
            index.getResponsesCount() != null ? index.getResponsesCount()
            .intValue() : 0;
    return index.getTotalPosts() - responsesCount
}

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

private BigDecimal getAverageTimeIssue(IssueIndex index){
    long resultInMillis
    EntityContainer lastVersionCnt = getContainer(index.source)
    if (lastVersionCnt == null) {
        warn ("issue not fount: ${index.source}")
        return BigDecimal.ZERO
    }
    
    int postIndex = 0
    Date begin = null
    int count = 0
    for (int i = 0; i < lastVersionCnt.versionsCount; i++) {
        EntityContainer issueCnt
        if (i == lastVersionCnt.versionsCount - 1) {
            issueCnt = lastVersionCnt
        } else {
            issueCnt = EntityStorage.get().load(index.source.type, index.source.uid, i)
        }
        BaseIssue issue = issueCnt?.entity
        if (issue == null) {
            warn ("issue version not found: ${index.source}. version: ${i}")
            resultInMillis = index.averageResponseTime != null ? index.averageResponseTime.longValue() : 0
            return new BigDecimal(resultInMillis / 1000d / 3600d)
        }
        if (issue.status == IssueStatus.CLOSED) {
            if (begin != null) {
                resultInMillis += issueCnt.versionInfo.created.getTime() - begin.getTime()
                count++
                begin = null
            }
        } else if (issue.status == IssueStatus.NEW) {
            if (begin == null) {
                begin = issueCnt.versionInfo.created
            }
        }
    }
    if (count == 0) {
        return BigDecimal.ZERO
    }
    resultInMillis /= count
    return new BigDecimal(resultInMillis / 1000d / 3600d)
}

private Date getCreateTime(IssueIndex index){
    return index.getCreateDate()
}

private Date getCreateDate(IssueIndex index){
    return index.getCreateDate()
}

private Date getLastCloseTime(IssueIndex index){
    return index.getLastCloseDate()
}

private Date getLastCloseDate(IssueIndex index){
    return index.getLastCloseDate()
}

private BigDecimal getActivityTime(IssueIndex index){
    if (index.getLastCloseDate() == null) {
        return BigDecimal.ZERO;
    }

    long time = index.getLastCloseDate().getTime() - index.getCreateDate().getTime();
    long timeSec = TimeUnit.MILLISECONDS.toSeconds(time)
    double res = timeSec / 3600.0

    return new BigDecimal(res)
}

private void prepareDate() {
    if (!parameters['SALES_POINT'].empty) {
        allTickets = allTickets.findAll { IssueIndex index ->
            try {
                if (index.salesPoint != null) {
                    return parameters['SALES_POINT'].contains(index.salesPoint)
                }
            } catch (Exception e) { }
            BaseIssue issue = getContainer(index.source)?.entity
            if (issue == null) {
                return false
            }
            BookingFile booking = getContainer(issue.booking)?.entity
            if (booking == null) {
                return false
            }
            if (booking.reservations.empty) {
                return false
            }
            return parameters['SALES_POINT'].contains(booking.reservations.get(0).salesPoint)
        }
    }
    try{
        allTickets.sort{IssueIndex a, IssueIndex b ->
            int cmp = 0;

            if ((a.getClient() != null) && (b.getClient() != null)) {
                cmp =a.getClient().getCaption().compareTo(b.getClient().getCaption());
            }

            if ((cmp == 0) && (a.getStatus() != null) && (b.getStatus() != null)) {
                cmp = a.getStatus().compareTo(b.getStatus());
            }

            if ((cmp == 0) && (a.getBookingNumber() != null) && (b.getBookingNumber() != null)) {
                cmp = a.getBookingNumber().compareTo(b.getBookingNumber());
            }

            return cmp;
        }
    }catch(Exception e){
        warn e.getMessage()
    }

    int not_order       = 0;
    int corr_all        = 0;
    int corr_ref_exc    = 0;
    int corr_req        = 0;
    Set<String> order   = new HashSet<String>();
    BigDecimal question_all = BigDecimal.ZERO;
    BigDecimal answer_all   = BigDecimal.ZERO;

    allTickets.each{IssueIndex index ->
        BigDecimal averateTimeIssue = getAverageTimeIssue(index)
        if (averateTimeIssue.compareTo(eps) < 0) {
            return
        }
        
        List<IssueInfo> list
        IssueStatistics stat
        IssueInfo issueInfo = new IssueInfo();
        
        if (this.mainMap.containsKey('list')) {
            list = this.mainMap.get('list')
        } else {
            list = new LinkedList<IssueInfo>()
            this.mainMap.put('list', list)
        }
        if (this.mainMap.containsKey('stat')) {
            stat = this.mainMap.get('stat')
        } else {
            stat = new IssueStatistics()
            this.mainMap.put('stat', stat)
        }

        corr_all++;
        if (index.getType() == IssueType.COMMON || index.getType() == IssueType.REFUND) {
            stat.commonsCount++;
            corr_ref_exc++;
        } else if (index.getType() == IssueType.SIMPLE) {
            stat.simpleCount++;
            corr_req++;
        }

        if (!TextUtil.isBlank(index.getBookingNumber())) {
            stat.bookingNumbers++
        }

        issueInfo.subagencyName = getSubagencyName(index);
        issueInfo.agencyName = getAgencyName(index);
        issueInfo.numberIssue   = getNumberIssue(index);

        String numberBooking = getNumberBooking(index);
        if(numberBooking == null || numberBooking.isEmpty()){
            issueInfo.numberBooking = 'без заказа';
            not_order++;
        } else {
            issueInfo.numberBooking = numberBooking;
            order.add(numberBooking);
        }

        issueInfo.queue         = getQueue(index);
        issueInfo.type          = getType(index);
        issueInfo.priority      = getPriority(index);
        issueInfo.author        = getAuthor(index);
        issueInfo.responsible   = getResponsible(index);
        issueInfo.amountResponse= getAmountResponse(index);
        issueInfo.amountQuestion= getAmountQuestion(index);
        issueInfo.averageTimeIssue  = averateTimeIssue;
        issueInfo.createTime        = getCreateTime(index);
        issueInfo.createDate        = getCreateDate(index);
        issueInfo.lastCloseTime     = getLastCloseTime(index);
        issueInfo.lastCloseDate     = getLastCloseDate(index);
        issueInfo.activityTime      = getActivityTime(index);

        question_all = MiscUtil.sum(question_all,new BigDecimal(issueInfo.getAmountQuestion().intValue()));
        answer_all = MiscUtil.sum(answer_all,new BigDecimal(issueInfo.getAmountResponse().intValue()));
        
        list.add(issueInfo);
    }
    
    this.mainStat.put(StatType.ORDER, order);
    this.mainStat.put(StatType.NOT_ORDER, new BigDecimal(not_order));
    this.mainStat.put(StatType.CORR_ALL, new BigDecimal(corr_all));
    this.mainStat.put(StatType.CORR_REF_EXC, new BigDecimal(corr_ref_exc));
    this.mainStat.put(StatType.CORR_REQ, new BigDecimal(corr_req));
    this.mainStat.put(StatType.QUESTION_ALL, question_all);
    this.mainStat.put(StatType.ANSWER_ALL, answer_all);
}

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

private void reportHead(){
    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((parameters.STATUS == null ? 'все' : parameters.STATUS.toString()), 'metadataValue');
    nextRow();text('Приоритет:','header4')
    nextColumn();
    text((parameters.PRIORITY == null ? 'все' : parameters.PRIORITY.toString()), 'metadataValue');
    nextRow();text('Тип:','header4');
    nextColumn();
    text((parameters.TYPE == null ? 'все' : parameters.TYPE.toString()), 'metadataValue');

    2.times{nextRow()}

    setStyle('header')
    rowHeight(3*scw)
    columnWidth(1.5*scw);text('Cубагентство','m1');
    nextColumn();columnWidth(1.5*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(1.5*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(1.5*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(1.5*scw);text('Время последнего закрытия','m1');
    nextColumn();columnWidth(1.5*scw);text('Дата последнего закрытия','m1');
    nextColumn();columnWidth(1.5*scw);text('Время активности, ч','m1');
}

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

private void body(){
    int scw = 10
    this.mainMap.get('list').each { IssueInfo info ->
        processNextCell(1, 0) {text(info.subagencyName,'textData')}
        processNextCell(0, 1) {text(info.numberIssue,'textData')}
        processNextCell(0, 1) {text(info.numberBooking,'textData')}
        processNextCell(0, 1) {text(info.queue,'textData')}
        processNextCell(0, 1) {text(info.type,'textData')}
        processNextCell(0, 1) {text(info.priority,'textData')}
        processNextCell(0, 1) {text(info.author,'textData')}
        processNextCell(0, 1) {text(info.responsible,'textData')}
        processNextCell(0, 1) {number(info.amountResponse,'numberDataInt')}
        processNextCell(0, 1) {number(info.amountQuestion,'numberDataInt')}
        processNextCell(0, 1) {number(info.averageTimeIssue, 'numberData')}
        processNextCell(0, 1) {date(info.createTime, 'timeData')}
        processNextCell(0, 1) {date(info.createDate, 'dateData')}
        processNextCell(0, 1) {date(info.lastCloseTime, 'timeData')}
        processNextCell(0, 1) {date(info.lastCloseDate, 'dateData')}
        processNextCell(0, 1) {number(info.activityTime, 'numberData')}
        rowHeight(1.3*scw)
    }
}
private void end(){
    IssueStatistics stat = this.mainMap.get('stat')
    processNextCell(2, 0) {text('Всего заказов','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.ORDER).size(),'numberDataInt')}

    processNextCell(1, 0) {text('Всего переписок','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.CORR_ALL),'numberDataInt')}

    processNextCell(1, 0) {text('Обменов/возвратов','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.CORR_REF_EXC),'numberDataInt')}

    processNextCell(1, 0) {text('Запросов','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.CORR_REQ),'numberDataInt')}

    processNextCell(1, 0) {text('Без заказа','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.NOT_ORDER),'numberDataInt')}
    
    processNextCell(2, 0) {text('Кол-во ответов','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.ANSWER_ALL),'numberDataInt')}
    
    processNextCell(1, 0) {text('Кол-во вопросов','textData')}
    processNextCell(0, 1) {number(this.mainStat.get(StatType.QUESTION_ALL),'numberDataInt')}
}

private 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: 'numberData', h_alignment: 'RIGHT', format: '#,##0.00', parent: 'data')
    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()
}

warn 'reportVersion=' + '0.0.2'
        