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.BaseProduct
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.booking.ProductIndex
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.Reservation
import com.gridnine.xtrip.common.model.booking.air.Product
import com.gridnine.xtrip.common.model.booking.air.SegmentTariff
import com.gridnine.xtrip.common.model.dict.Airline
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.helpers.ProfileHelper
import com.gridnine.xtrip.common.model.helpers.SearchQueryHelper
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.util.TextUtil
import groovy.transform.Field
import org.apache.commons.lang.StringUtils

import java.util.stream.Collectors

class AeroTicket
{
    String carrier
    String subAgent
    Date issueDate
    String pnr
    int segment
    String gdsName
}

@Field String specifiedAgencies = ''
@Field String specifiedSubAgents = ''
@Field String specifiedCarrier = ''
@Field String specifiedGds = ''

@Field Integer segmentsSum = 0

processIncomingData()
createSpreadsheetStyles()
composeReport()

@Field private Map<String, Integer> totalMap = new HashMap<String, Integer>()



@Field private final Set<String> bookingNumbers = new HashSet<String>()

private String getSpecifiedAgencies()
{
    List objectList = parameters['AGENCY']
    String result = StringUtils.join(objectList, ', ')
    return result
}

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 getSpecifiedCarriers()
{
    def carrier = parameters['CARRIER']
    return carrier == null ? '' : carrier.toString()
}

private String getSpecifiedGds()
{
    def gds = parameters['RESERVATION_SYSTEM']
    return gds == null ? '' : gds.toString()
}

private void processIncomingData()
{
    this.specifiedAgencies = getSpecifiedAgencies()
    this.specifiedSubAgents = getSpecifiedSubAgents()
    this.specifiedCarrier = getSpecifiedCarriers()
    this.specifiedGds = getSpecifiedGds()
}

private void createSpreadsheetStyles()
{
    createStyle(name: 'title', fontBold: true, h_span: 6, 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: 'preliminaryTotalText', parent: 'data', h_span: 4, fontBold: true,
                h_alignment: 'RIGHT')
    createStyle(name: 'finalTotalText', parent: 'data', h_span: 2, fontBold: true, h_alignment: 'RIGHT')
    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<AeroTicket> ticketList = []
        allTickets.each { ProductIndex index ->
            if (index.getStatus() == ProductStatus.SELL)
            {
                AeroTicket ticket = new AeroTicket()
                EntityStorage entityStorage = Environment.getPublished(EntityStorage.class);
                def container = entityStorage.get().resolve(index.source)
                if ((container == null) || (container.getEntity() == null) || this.bookingNumbers.contains(container.getEntity().getNumber()))
                {
                    return
                }
                this.bookingNumbers.add(container.getEntity().getNumber());
                BaseProduct prod = BookingHelper.findProductByUid(index.navigationKey, container.entity)
                if (!prod)
                {
                    return
                }
                else if (prod instanceof Product)
                {
                    for (String ticketNumber : index.getTicketNumbers())
                    {
                        populateSubAgent(index, entityStorage, ticket)
                        populateIssueDate(index, ticket)
                        boolean isAeroBooking = populateSegment(container, ticket)
                        if (!isAeroBooking)
                        {
                            continue
                        }
                        populateCarrier(index, ticket)
                        populatePnr(index, ticket)
                        populateGDS(index, ticket)
                        segmentsSum += ticket.segment
                        ticketList.add(ticket)
                    }
                }
            }
        }

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

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

private void fillTableHeader()
{
    int colWidth = 20
    processNextCell(2, 0) {
        setStyle('header')
        rowHeight(40)
    }
    processNextCell(0, 0) {
        columnWidth(colWidth)
        text('Перевозчик')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Наименование субагента')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Дата выписки')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('PNR')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сегменты')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Система бронирования')
    }
}

private void fillTable(List<AeroTicket> ticketList)
{
    ticketList = ticketList.sort { AeroTicket a, AeroTicket b ->
        a.carrier <=> b.carrier ?: a.subAgent <=> b.subAgent ?: a.issueDate <=> b.issueDate
    }

    AeroTicket currentTicket
    AeroTicket 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?.carrier, 'textData')
            rowHeight(12)
        }
        processNextCell(0, 1) {
            text(currentTicket?.subAgent, 'textData')
        }
        processNextCell(0, 1) {
            date(currentTicket?.issueDate, 'dateData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.pnr, 'textData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.segment, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.gdsName, 'textData')
        }

        fillTotalMap(currentTicket)
        fillTotalMap(currentTicket)

        boolean isCarrierChanged = !currentTicket?.carrier?.equals(nextTicket?.carrier)
        boolean isSubAgentChanged = !currentTicket?.subAgent?.equals(nextTicket?.subAgent)
        boolean needTotalForCarrier = isCarrierChanged
        boolean needTotalForSubAgent = isSubAgentChanged

        if (needTotalForCarrier)
        {
            fillPreliminaryTotalRow(currentTicket.carrier)
        }

        if (needTotalForSubAgent)
        {
            fillPreliminaryTotalRow(currentTicket.subAgent)
            dropTotalMap(currentTicket)
        }
        currentTicket = nextTicket
    }
}

private void fillPreliminaryTotalRow(String targetObject)
{
    Integer segments = (totalMap.get(targetObject) ?: BigDecimal.ZERO)

    processNextCell(1, 0) {
        text(String.format('Итого сегментов по %s:', targetObject), 'preliminaryTotalText')
    }
    processNextCell(0, 1) {
        number(segments, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        text('', 'data')
    }
}

private void dropTotalMap(AeroTicket ticket)
{
    totalMap.put(ticket.subAgent, 0)
}

private void fillTotalMap(AeroTicket ticket)
{
    calculateTotalValueForObject(totalMap, ticket.carrier, ticket.segment)
    calculateTotalValueForObject(totalMap, ticket.subAgent, ticket.segment)
}

private void calculateTotalValueForObject(Map<String, BigDecimal> map, String objectKey, Integer valueToProcess)
{
    BigDecimal totalFareForSubAgent = map.get(objectKey)
    if (!totalFareForSubAgent)
    {
        map.put(objectKey, valueToProcess)
    }
    else
    {
        map.put(objectKey, totalFareForSubAgent.add(valueToProcess))
    }
}

private void fillTableFooter()
{
    processNextCell(1, 2) {
        text('ИТОГО:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(segmentsSum, 'totalIntegerNumber')
    }
}

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

private void populateSubAgent(ProductIndex index, EntityStorage entityStorage, AeroTicket 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(ProductIndex index, AeroTicket ticket)
{
    if (index.getIssueDate() != null)
    {
        ticket.setIssueDate(index.getIssueDate())
    }
}

private void populateGDS(ProductIndex index, AeroTicket ticket)
{
    if (index.getDisplayedGdsName() != null) {
        ticket.setGdsName(index.getDisplayedGdsName().toString())
    } else if (index.getGdsName() != null) {
        ticket.setGdsName(index.getGdsName().toString())
    } else {
        ticket.setGdsName("")
    }
}

private void populateCarrier(ProductIndex index, AeroTicket ticket)
{
    String carrierStr = "";
    Locale ruLocale = new Locale("RU");
    DictionaryCache dictCache = Environment.getPublished(DictionaryCache.class);
    if (index.getCarrier() != null)
    {
        Airline carrier = dictCache.resolveReference(index.getCarrier());
        if (carrier != null)
        {
            carrierStr = !TextUtil.isBlank(carrier.getTranslations().get(ruLocale)) ?
                    carrier.getTranslations().get(ruLocale) : carrier.getTranslations().get(Locale.ENGLISH);
            if (TextUtil.isBlank(carrierStr))
            {
                Iterator<Map.Entry<Locale, String>> iter = carrier.getTranslations().entrySet().iterator();
                if (iter.hasNext())
                {
                    carrierStr = iter.next().getValue();
                }
            }
        }
    }
    ticket.setCarrier(carrierStr);
}

private void populatePnr(ProductIndex index, AeroTicket ticket)
{
    if (TextUtil.nonBlank(index.getDisplayedRecordLocator())) {
        ticket.setPnr(index.getDisplayedRecordLocator())
    } else {
        ticket.setPnr(index.getRecordLocator())
    }
}

private boolean populateSegment(EntityContainer<BookingFile> container, AeroTicket ticket)
{
    boolean aviaBooking = false
    int count = 0
    outer: for (Reservation res : container.getEntity().getReservations()) {
        List<BaseProduct> pp = res.getProducts()

        inner: for (BaseProduct p : pp) {
            if (!(p instanceof Product)) {
                continue outer
            }
            Product product = (Product) p

            if ((product.getStatus() != ProductStatus.SELL)
                    || (product.getNextProduct() != null)) {
                continue inner
            }

            aviaBooking = true
            for (SegmentTariff tariff : product.getSegmentTariffs()) {
                count += tariff.getSegments().size()
            }
        }
    }
    ticket.setSegment(count)
    return aviaBooking
}