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

import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.l10n.model.L10nStringHelper
import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.booking.*
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.handlers.HandlersRegistry
import com.gridnine.xtrip.common.model.handlers.ProductHandler
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.helpers.ProfileHelper
import com.gridnine.xtrip.common.model.profile.Organization
import groovy.transform.Field
import org.apache.commons.lang.StringUtils

class AeroExpressTicket
{
    String subAgent
    String agent
    ProductStatus productStatus
    Date issueDate
    String ticketNumber
    int segment
    BigDecimal price
    AeroexpressClassOfService classOfService
    boolean hasGdsErrors
}

class TotalKey
{
    TotalKey(String object, AeroexpressClassOfService cos, String status)
    {
        this.object = object
        this.cos = cos
        this.status = status
    }

    String object
    String cos
    String status

    boolean equals(o)
    {
        if (this.is(o))
        {
            return true
        }
        if (getClass() != o.class)
        {
            return false
        }

        TotalKey totalKey = (TotalKey) o

        if (cos != totalKey.cos)
        {
            return false
        }
        if (object != totalKey.object)
        {
            return false
        }
        if (status != totalKey.status)
        {
            return false
        }

        return true
    }

    int hashCode()
    {
        int result
        result = (object != null ? object.hashCode() : 0)
        result = 31 * result + (cos != null ? cos.hashCode() : 0)
        result = 31 * result + (status != null ? status.hashCode() : 0)
        return result
    }
}

class Total
{
    BigDecimal price
    int segment

    Total(BigDecimal price, int segment)
    {
        this.price = price
        this.segment = segment
    }
}

@Field String specifiedSubAgents = ''
@Field String specifiedAgents = ''
@Field boolean isGroupedByAgent = false

processIncomingData()
createSpreadsheetStyles()
composeReport()

@Field private Map<TotalKey, Total> subAgentTotalMap = new HashMap<TotalKey, Total>()
@Field private Map<TotalKey, Total> agentTotalMap = new HashMap<TotalKey, Total>()

private String getSpecifiedSubAgents()
{
    List objectList = parameters['SUBAGENT']
    String result = StringUtils.join(new ArrayList() {
        {
            objectList.each {
                def object = EntityStorage.get().resolve(it)?.getEntity()
                add(ProfileHelper.getFullName(object, LocaleHelper.getCurrentLocale(), false))
            }
        }
    }, ', ')
    return result
}

private String getSpecifiedAgents()
{
    List objectList = parameters['AGENT']
    String result = StringUtils.join(new ArrayList() {
        {
            objectList.each {
                def object = EntityStorage.get().resolve(it)?.getEntity()
                add(ProfileHelper.getFullName(object, LocaleHelper.getCurrentLocale(), false))
            }
        }
    }, ', ')
    return result
}

private boolean isGroupedByAgent()
{
    return parameters['GROUP_BY_AGENT']
}

private void processIncomingData()
{
    this.specifiedSubAgents = getSpecifiedSubAgents()
    this.specifiedAgents = getSpecifiedAgents()
    this.isGroupedByAgent = isGroupedByAgent()
}

private void createSpreadsheetStyles()
{
    createStyle(name: 'title', fontBold: true, h_span: getColumnShiftTotal(8), h_alignment: 'CENTER',
                v_alignment: 'CENTER', fontHeight: 20)
    createStyle(name: 'metadataTitle', fontBold: true, h_span: 2, 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: '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', format: '#,##0.00')
    createStyle(name: 'dateData', parent: 'data', h_alignment: 'RIGHT', format: 'm/d/yy')
    createStyle(name: 'finalPreliminaryTotalText', parent: 'data', h_span: getColumnShiftTotal(4), fontBold: true,
                h_alignment: 'RIGHT')
    createStyle(name: 'boldedTextData', parent: 'textData', fontBold: true)
    createStyle(name: 'finalTotalText', parent: 'data', h_span: 2, fontBold: true, h_alignment: 'RIGHT')
    createStyle(name: 'centeredFinalTotalText', parent: 'data', h_span: 2, fontBold: true)
    createStyle(name: 'finalTotalHeaderText', parent: 'data', fontBold: true, h_alignment: 'RIGHT')
    createStyle(name: 'totalNumber', fontBold: true, parent: 'numberData')
    createStyle(name: 'totalIntegerNumber', fontBold: true, parent: 'numberData', format: '0')
}

private void composeReport()
{
    page { 'Авиа билеты' } {
        List<AeroExpressTicket> ticketList = []
        allTickets.each { AeroexpressTicketIndex index ->
            AeroExpressTicket ticket = new AeroExpressTicket()
            EntityStorage entityStorage = Environment.getPublished(EntityStorage.class);
            ProductHandler<AeroexpressTicket> handler = Environment.getPublished(
                    HandlersRegistry.class).findProductHandler(AeroexpressTicket.class);
            EntityContainer<BookingFile> container = entityStorage.get().resolve(index.source)
            if ((container == null) || (container.getEntity() == null))
            {
                return
            }
            BaseProduct prod = BookingHelper.findProductByUid(index.navigationKey, container.entity)
            if ((prod == null) || !(prod instanceof AeroexpressTicket))
            {
                return
            }
            AeroexpressTicket product = (AeroexpressTicket) prod

            populateSubAgent(index, entityStorage, ticket)
            if (isGroupedByAgent || !specifiedAgents.isEmpty())
            {
                populateAgent(index, container, ticket)
            }
            populateProductStatus(index, ticket)
            populateIssueDate(index, ticket)
            populateTicketNumber(index, ticket)
            populateSegment(index, ticket)
            populatePrice(index, product, handler, ticket)
            populateClassOfService(index, ticket)
            populateGdsError(index, ticket)
            ticketList.add(ticket)
        }

        fillSpreadSheetHeader()
        fillTableHeader()
        fillTable(ticketList)
        fillTableFooter()
    }
}

private void fillSpreadSheetHeader()
{
    processNextCell(0, 0) {
        rowHeight(30)
        text('Отчет по Аэроэкспресс', 'title')
    }
    processNextCell(2, 0) {
        rowHeight(12)
        text('Приложение к отчету агента', 'metadataTitle')
    }
    processNextCell(0, 1) {
        text('№', 'metadataTitle')
    }
    processNextCell(0, 1) {
        text('от', 'metadataTitle')
    }
    processNextCell(2, 0) {
        text('Дата составления отчета:', 'metadataTitle')
    }
    processNextCell(0, 1) {
        date(new Date(), 'metadataDateValue')
    }
    processNextCell(1, 0) {
        text('Период:', 'metadataTitle')
    }
    processNextCell(0, 1) {
        text(parameters.REPORT_PERIOD, 'metadataValue')
    }
    if (!specifiedSubAgents.isEmpty())
    {
        processNextCell(1, 0) {
            text('Субагент:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedSubAgents, 'metadataValue')
        }
    }
    if (!specifiedAgents.isEmpty())
    {
        processNextCell(1, 0) {
            text('Агенты:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedAgents, 'metadataValue')
        }
    }
}

private void fillTableHeader()
{
    int colWidth = 20
    processNextCell(2, 0) {
        setStyle('header')
        rowHeight(40)
        columnWidth(colWidth)
        text('Наименование субагента')
    }
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        processNextCell(0, 1) {
            columnWidth(colWidth)
            text('Выписывающий агент')
        }
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Статус')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Дата выписки')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Номер билета')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сегменты')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сумма')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Класс обслуживания')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Ошибки GDS')
    }
}

private void fillTable(List<AeroExpressTicket> ticketList)
{
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        ticketList = ticketList.sort { AeroExpressTicket a, AeroExpressTicket b ->
            a.subAgent <=> b.subAgent ?:
                    a.agent <=> b.agent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else
    {
        ticketList = ticketList.sort { AeroExpressTicket a, AeroExpressTicket b ->
            a.subAgent <=> b.subAgent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }

    AeroExpressTicket currentTicket
    AeroExpressTicket nextTicket
    if (ticketList.size() > 0)
    {
        currentTicket = ticketList.first()
    }
    for (int i = 1; i <= ticketList.size(); i++)
    {
        if (i != ticketList.size())
        {
            nextTicket = ticketList.get(i)
        }
        else
        {
            nextTicket = null
        }
        processNextCell(1, 0) {
            text(currentTicket?.subAgent, 'textData')
        }
        if (isGroupedByAgent || !specifiedAgents.isEmpty())
        {
            processNextCell(0, 1) {
                text(currentTicket?.agent, 'textData')
            }
        }
        processNextCell(0, 1) {
            text(currentTicket.productStatus.toString(), 'textData')
        }
        processNextCell(0, 1) {
            date(currentTicket?.issueDate, 'dateData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.ticketNumber, 'textData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.segment, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.price, 'numberData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.classOfService?.toString(), 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.hasGdsErrors ? 'Да' : 'Нет', 'textData')
        }

        fillTotalMap(currentTicket, currentTicket.productStatus.toString())

        boolean isAgentChanged = !currentTicket?.agent?.equals(nextTicket?.agent)
        boolean isSubAgentChanged = !currentTicket?.subAgent?.equals(nextTicket?.subAgent)
        boolean isStatusChanged = !currentTicket?.productStatus?.equals(nextTicket?.productStatus)
        boolean needTotalForAgent = (isGroupedByAgent || !specifiedAgents.isEmpty()) &&
                ((!isAgentChanged && isStatusChanged) || isAgentChanged)
        boolean needTotalForSubAgent = isSubAgentChanged

        if (needTotalForAgent)
        {
            if (isStatusChanged || isAgentChanged)
            {
                fillPreliminaryTotalRow(agentTotalMap, currentTicket.agent, AeroexpressClassOfService.COMMON,
                                        currentTicket.productStatus.toString())
                fillPreliminaryTotalRow(agentTotalMap, currentTicket.agent, AeroexpressClassOfService.BUSINESS,
                                        currentTicket.productStatus.toString())
            }

            if (isAgentChanged)
            {
                fillPreliminaryTotalRow(agentTotalMap, currentTicket.agent, AeroexpressClassOfService.COMMON)
                fillPreliminaryTotalRow(agentTotalMap, currentTicket.agent, AeroexpressClassOfService.BUSINESS)
            }
        }

        if (needTotalForSubAgent)
        {
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.COMMON,
                                    ProductStatus.SELL.toString())
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.BUSINESS,
                                    ProductStatus.SELL.toString())
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.COMMON,
                                    ProductStatus.REFUND.toString())
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.BUSINESS,
                                    ProductStatus.REFUND.toString())
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.COMMON)
            fillPreliminaryTotalRow(subAgentTotalMap, currentTicket.subAgent, AeroexpressClassOfService.BUSINESS)
        }
        currentTicket = nextTicket
        rowHeight(12)
    }
}

private void fillPreliminaryTotalRow(Map<TotalKey, Total> map, String targetObject, AeroexpressClassOfService cos,
        String status = '')
{
    Total total = map.get(new TotalKey(targetObject, cos, status))
    if (total && !(total.price.equals(BigDecimal.ZERO) && total.segment == 0))
    {
        processNextCell(1, 0) {
            text(status.equals('') ? String.format('Итого по %s:', targetObject) :
                         String.format('Итого операции %s по %s:', status, targetObject), 'finalPreliminaryTotalText')
        }
        processNextCell(0, 1) {
            number(total.segment, 'totalNumber')
        }
        processNextCell(0, 1) {
            number(total.price, 'totalNumber')
        }
        processNextCell(0, 1) {
            text(cos.toString(), 'boldedTextData')
        }
        processNextCell(0, 1) {
            text('', 'data')
        }
    }
}

private void fillTotalMap(AeroExpressTicket currentTicket, String statusString)
{
    Total total = new Total(currentTicket.price, currentTicket.segment)
    TotalKey subAgentTotalKey = new TotalKey(currentTicket.subAgent, currentTicket.getClassOfService(), statusString)
    calculateTotalValueForObject(subAgentTotalMap, subAgentTotalKey, total)
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        TotalKey agentTotalKey = new TotalKey(currentTicket.agent, currentTicket.getClassOfService(), statusString)
        calculateTotalValueForObject(agentTotalMap, agentTotalKey, total)
    }
}

private void calculateTotalValueForObject(Map<TotalKey, Total> map, TotalKey totalKey, Total total)
{
    Total totalValue = map.get(totalKey)
    if (totalValue == null)
    {
        map.put(totalKey, total)
    }
    else
    {
        totalValue.price = totalValue.price.add(total.price)
        totalValue.segment += total.segment
        map.put(totalKey, totalValue)
    }
}

private void fillTableFooter()
{
    processNextCell(1, 0) {}
    processNextCell(1, 2) {
        text('Продажа', 'centeredFinalTotalText')
    }
    processNextCell(0, 1) {
        text('Возврат', 'centeredFinalTotalText')
    }
    processNextCell(0, 1) {
        text('Итого', 'centeredFinalTotalText')
    }
    fillTotalRow(AeroexpressClassOfService.COMMON)
    fillTotalRow(AeroexpressClassOfService.BUSINESS)
    fillTotalRow()
}

private void fillTotalRow(AeroexpressClassOfService cos = null)
{
    Total sellTotal = new Total(BigDecimal.ZERO, 0)
    Total refundTotal = new Total(BigDecimal.ZERO, 0)

    subAgentTotalMap.each { key, value ->
        if (key.getCos().equals(cos.toString()) || cos.equals(null))
        {
            if (key.status.equals(ProductStatus.SELL.toString()))
            {
                sellTotal.price = sellTotal.price.add(value.price)
                sellTotal.segment = sellTotal.segment + value.segment
            }
            else
            {
                refundTotal.price = refundTotal.price.add(value.price)
                refundTotal.segment = refundTotal.segment + value.segment
            }
        }
    }

    processNextCell(1, 0) {
        text(cos == null ? 'Общий/Бизнес' : cos.toString(), 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(sellTotal.segment, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(sellTotal.price, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(refundTotal.segment, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(refundTotal.price, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(sellTotal.segment + refundTotal.segment, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(sellTotal.price + refundTotal.price, 'totalNumber')
    }
}

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

private int getColumnShiftTotal(int baseShift)
{
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        baseShift++
    }
    return baseShift
}

private void populateSubAgent(AeroexpressTicketIndex index, EntityStorage entityStorage, AeroExpressTicket ticket)
{
    String subAgent = ''
    if (index.getSubagency() != null)
    {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getSubagency());
        if (cont != null)
        {
            Organization org = cont.getEntity();
            if (org != null)
            {
                subAgent = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
            }
        }
    }
    ticket.setSubAgent(subAgent);
}

private void populateIssueDate(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    if (index.getIssueDate() != null)
    {
        ticket.setIssueDate(index.getIssueDate())
    }
}

private void populateProductStatus(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    if (index.getStatus() != null)
    {
        ticket.setProductStatus(index.getStatus())
    }
}

private void populateAgent(AeroexpressTicketIndex index, EntityContainer<BookingFile> container,
        AeroExpressTicket ticket)
{
    String agent = ''
    if (container.getEntity().getCustomerProfile() != null)
    {
        if (container.getEntity().getCustomerProfile().equals(ProfileHelper.getRetailProfileContainer().toReference()))
        {
            if (index.getAgent() != null)
            {
                agent = index.getAgent().getCaption()
            }
        }
        else
        {
            agent = container.getEntity().getCustomerProfile().getCaption()
        }
    }
    ticket.setAgent(agent)
}

private void populateTicketNumber(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    if (index.getTicketNumber() != null)
    {
        ticket.setTicketNumber(index.getTicketNumber())
    }
    else
    {
        ticket.setTicketNumber('')
    }
}

private void populatePrice(AeroexpressTicketIndex index, AeroexpressTicket product,
        ProductHandler<AeroexpressTicket> handler, AeroExpressTicket ticket)
{
    BigDecimal price = BigDecimal.ZERO;
    if ((index.getEquivalentFare() != null) && (index.getStatus() != ProductStatus.VOID))
    {
        price = handler.calculateProductPrice(product, ContractType.VENDOR).getTotal();
    }
    ticket.setPrice(price);
}

private void populateSegment(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    ticket.setSegment(index.getStatus() == ProductStatus.REFUND ? -1 : 1)
}

private void populateClassOfService(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    ticket.setClassOfService(index.getClassOfService())
}

private void populateGdsError(AeroexpressTicketIndex index, AeroExpressTicket ticket)
{
    ticket.setHasGdsErrors(index.isGdsError())
}