import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.booking.OperationBatch
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.system.PaymentType
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicket
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicketType
import com.gridnine.xtrip.common.util.MiscUtil

import java.text.SimpleDateFormat
import java.time.Month
import java.time.ZoneId

createStyle(name: "left", h_alignment: "LEFT")
createStyle(name: "right", h_alignment: "RIGHT")
createStyle(name: "center", h_alignment: "CENTER")
createStyle(name: "left-bold", h_alignment: "LEFT", fontBold : true)
createStyle(name: "right-bold", h_alignment: "RIGHT", fontBold : true)
createStyle(name: "center-bold", h_alignment: "CENTER", fontBold : true)
createStyle(name: "center-center-bold", h_alignment: "CENTER", v_alignment: "CENTER", fontBold : true)
createStyle(name: "left-bold-border", h_alignment: "LEFT", fontBold : true, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "right-bold-border", h_alignment: "RIGHT", fontBold : true, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-bold-border", h_alignment: "CENTER", fontBold : true, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-bold-border-format", h_alignment: "CENTER", fontBold : true, format: '#,##0.00', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-center-bold-border", h_alignment: "CENTER", v_alignment: "CENTER", fontBold : true, leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-bold-big", h_alignment: "CENTER", fontBold : true, fontHeight : 12)
createStyle(name: "left-border", h_alignment: "LEFT", leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "right-border", h_alignment: "RIGHT", leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-border", h_alignment: "CENTER", leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)
createStyle(name: "center-border-format", h_alignment: "CENTER", format: '#,##0.00', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', wrapText: true)

def printTable() {
    Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets = collectTickets()

    printTitle(statusToTickets)
    printHeader(statusToTickets)
    printTableContent(statusToTickets)
    printSignatures()
}

def printTitle(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    rowHeight(30)
    nextColumn(); text("Агентство " + parameters['AGENCY_NAME'], "center-bold-big", Math.max(months.size(), 1) + 2, 1)
    (Math.max(months.size(), 1) + 2).times {
        nextColumn()
    }
    text("Форма №1", "center")

    nextRow()
    rowHeight(30)
    text("Сводный отчет о продаже авиаперевозок а/к \"КАП\"", "center-bold-big", Math.max(months.size(), 1) + 4, 1)

    SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy")
    def periodBeginParameter = parameters['key-report-params']?.periodBegin
    def periodEndParameter = parameters['key-report-params']?.periodEnd

    nextRow()
    text("за период с " +
            (periodBeginParameter ? sdf.format(periodBeginParameter) : "") +
            " по " +
            (periodEndParameter ? sdf.format(periodEndParameter) : ""),
            "center-bold-big", Math.max(months.size(), 1) + 4, 1)
}

def printHeader(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    rowHeight(15)
    text("", "center-center-bold-border", 1, 2)

    nextColumn(); text("Раздел", "center-center-bold-border", 1, 2)

    nextColumn(); text("Всего", "center-center-bold-border", 1, 2)

    nextColumn(); text("К-во а/билетов", "center-center-bold-border", 1, 2)

    nextColumn(); text("в том числе по месяцам:", "center-center-bold-border", Math.max(months.size(), 1), 1)

    nextRow()
    3.times {
        nextColumn()
    }

    SimpleDateFormat sdf = new SimpleDateFormat("MMMM", LocaleHelper.RU_LOCALE)

    months.each {
        nextColumn()

        GregorianCalendar calendar = new GregorianCalendar()
        calendar.set(Calendar.MONTH, it.ordinal())
        calendar.set(Calendar.DAY_OF_MONTH, 1)
        text(sdf.format(calendar.getTime()))
    }
}

def printTableContent(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    printBeginPeriodBalance(statusToTickets)
    printStatusSubTable(statusToTickets, ProductStatus.SELL)
    printStatusSubTable(statusToTickets, ProductStatus.EXCHANGE)
    printStatusSubTable(statusToTickets, ProductStatus.REFUND)

    printCommissionsSubTable(statusToTickets)
    printPenaltiesSubTable(statusToTickets)
    printTotal(statusToTickets)
    printEndPeriodBalance(statusToTickets)
}

def printStatusSubTable(
        Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets,
        ProductStatus status) {
    Map<Month, List<AirTicketsTemplateReportTicket>> monthToTickets = statusToTickets.get(status)
    Set<Month> months = collectMonths(statusToTickets)

    int startIndex
    switch (status) {
        case ProductStatus.SELL:
            startIndex = 1
            break
        case ProductStatus.EXCHANGE:
            startIndex = 2
            break
        case ProductStatus.REFUND:
            startIndex = 3
            break
        default:
            throw new IllegalArgumentException(status.name())
    }

    boolean printPenalty = status == ProductStatus.REFUND

    nextRow()
    rowHeight(30, false)
    text(startIndex + "", "left-border")
    nextColumn()
    switch (status) {
        case ProductStatus.SELL:
            text("Выручка по реестрам продажи авиабилетов" + "\n" + "(1.1 - 1.1.1 - 1.1.2 - 1.1.3 - 1.1.4 + 1.2)")
            break
        case ProductStatus.EXCHANGE:
            text("Выручка по реестрам обмена авиабилетов" + "\n" + "(2.1 - 2.1.1 - 2.1.2 - 2.1.3 - 2.1.4 + 2.2)")
            break
        case ProductStatus.REFUND:
            text("Выручка по реестрам возврата авиабилетов" + "\n" + "(3.1 - 3.1.1 - 3.1.2 - 3.1.3 - 3.1.4 - 3.2 - 3.3)")
            break
    }
    nextColumn(); printTariffFormula(status)
    nextColumn(); text("")
    months.size().times {
        nextColumn(); printTariffFormula(status)
    }


    nextRow()
    text(startIndex + ".1", "left-border")
    nextColumn(); text("Тариф")
    nextColumn(); number(getTariff(getAllTickets(monthToTickets)), "center-border-format")
    nextColumn(); number(getTicketsCount(monthToTickets), "center-border")
    months.each {
        nextColumn(); number(getTariff(monthToTickets.get(it)), "center-border-format")
    }

    nextRow()
    text("", "left-border")
    nextColumn(); text("в том числе:", "left-border", months.size() + 3, 1)

    nextRow()
    text(startIndex + ".1.1", "left-border")
    nextColumn(); text("в счет ВЗР")
    nextColumn(); number(getTariffByPaymentType(getAllTickets(monthToTickets), PaymentType.INTERLINE), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn(); number(getTariffByPaymentType(monthToTickets.get(it), PaymentType.INTERLINE))
    }

    nextRow()
    text(startIndex + ".1.2", "left-border")
    nextColumn(); text("по ВПД")
    nextColumn(); number(getTariffByPaymentTypes(getAllTickets(monthToTickets), PaymentType.MTD, PaymentType.CREDIT), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn(); number(getTariffByPaymentTypes(monthToTickets.get(it), PaymentType.MTD, PaymentType.CREDIT))
    }

    nextRow()
    text(startIndex + ".1.3", "left-border")
    nextColumn(); text("по служебным требованиям")
    nextColumn(); text("")
    nextColumn(); text("")
    months.each {
        nextColumn(); text("") // don`t know what to calculate
    }

    nextRow()
    text(startIndex + ".1.4", "left-border")
    nextColumn(); text("по документам соцзащиты")
    nextColumn(); text("")
    nextColumn(); text("")
    months.each {
        nextColumn(); text("") // don`t know what to calculate
    }

    if (printPenalty) {
        nextRow()
        text(startIndex + ".2", "left-border")
        nextColumn(); text("Штраф")
        nextColumn(); number(getPenalty(getAllTickets(monthToTickets)), "center-border-format")
        nextColumn(); text("")
        months.each {
            nextColumn(); number(getPenalty(monthToTickets.get(it)))
        }
    }

    nextRow()
    text(startIndex + "." + (printPenalty ? "3" : "2"), "left-border")
    nextColumn(); text("Прочие сборы")
    nextColumn(); number(getTaxesSum(getAllTickets(monthToTickets)), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn(); number(getTaxesSum(monthToTickets.get(it)))
    }
}

def printTariffFormula(ProductStatus status) {
    switch (status) {
        case ProductStatus.SELL:
        case ProductStatus.EXCHANGE:
            formula("${cellIndex(1, 0)}-${cellIndex(3, 0)}-${cellIndex(4, 0)}-${cellIndex(5, 0)}-${cellIndex(6, 0)}+${cellIndex(7, 0)}", "center-bold-border-format")
            break
        case ProductStatus.REFUND:
            formula("${cellIndex(1, 0)}-${cellIndex(3, 0)}-${cellIndex(4, 0)}-${cellIndex(5, 0)}-${cellIndex(6, 0)}-${cellIndex(7, 0)}-${cellIndex(8, 0)}", "center-bold-border-format")
            break
    }
}

def printCommissionsSubTable(
        Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    rowHeight(30, false)
    text("4", "left-border")
    nextColumn(); text("Комиссионное вознаграждение" + "\n" + "(4.1 + 4.2 - 4.3)")
    nextColumn(); formula("${cellIndex(1, 0)}+${cellIndex(2, 0)}-${cellIndex(3, 0)}", "center-bold-border-format")

    nextColumn(); text("")
    months.each {
        nextColumn(); formula("${cellIndex(1, 0)}+${cellIndex(2, 0)}-${cellIndex(3, 0)}")
    }

    nextRow()
    rowHeight(30, false)
    text("4.1", "left-border")
    nextColumn(); text("По реестрам продажи авиабилетов" + "\n" + "(1.1 х 1%)")
    nextColumn(); number(getVendorCommissionValue(getAllTickets(statusToTickets.get(ProductStatus.SELL))), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        number(getVendorCommissionValue(statusToTickets.get(ProductStatus.SELL).get(it)))
    }

    nextRow()
    rowHeight(30, false)
    text("4.2", "left-border")
    nextColumn(); text("По реестрам обмена авиабилетов" + "\n" + "(2.1 х 1%)")
    nextColumn(); number(getVendorCommissionValue(getAllTickets(statusToTickets.get(ProductStatus.EXCHANGE))), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        number(getVendorCommissionValue(statusToTickets.get(ProductStatus.EXCHANGE).get(it)))
    }

    nextRow()
    rowHeight(30, false)
    text("4.3", "left-border")
    nextColumn(); text("По реестрам возврата авиабилетов" + "\n" + "(3.1 х 1%)")
    nextColumn(); number(getVendorCommissionValue(getAllTickets(statusToTickets.get(ProductStatus.REFUND))), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        number(getVendorCommissionValue(statusToTickets.get(ProductStatus.REFUND).get(it)))
    }
}

def printPenaltiesSubTable(
        Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    rowHeight(30, false)
    text("5", "left-border")
    nextColumn(); text("Сумма выручки, восстановленная по" + "\n" + "замечаниям Перевозчика")
    nextColumn(); text("", "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        text("")
    }

    nextRow()
    rowHeight(30, false)
    text("6", "left-border")
    nextColumn(); text("Сумма начетов и штрафов по" + "\n" + "претензиям Перевозчика")
    nextColumn(); number(getAgencyMemoPenalties(), "center-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        text("")
    }

    nextRow()
    text("7", "left-border")
    nextColumn(); text("Сумма штрафов за испорченные БСО")
    nextColumn(); text("")
    nextColumn(); text("")
    months.each {
        nextColumn()
        text("")
    }
}

def printTotal(
        Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    text("8", "left-border")
    nextColumn(); text("ИТОГО подлежит перечислению" + "\n" + "(1 + 2 - 3 - 4 + 5 + 6 + 7)")
    rowHeight(30, false)
    nextColumn(); formula("${cellIndex(-32, 0)}+${cellIndex(-24, 0)}-${cellIndex(-16, 0)}" +
            "-${cellIndex(-7, 0)}+${cellIndex(-3, 0)}+${cellIndex(-2, 0)}+${cellIndex(-1, 0)}", "center-bold-border-format")
    nextColumn(); text("")
    months.each {
        nextColumn()
        text("")
    }
}

def printSignatures() {
    nextRow(); nextColumn(); rowHeight(45, false)
    text("Руководитель", "left-bold")

    nextRow(); nextColumn(); rowHeight(45, false)
    text("Главный бухгалтер", "left-bold")

    nextRow(); nextColumn(); rowHeight(45, false)
    text("Исполнитель", "left-bold")
}


def printBeginPeriodBalance(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    text("", "left-border"); nextColumn()
    text("Сальдо на начало периода", "left-border"); nextColumn()
    text("", "center-border-format"); nextColumn()
    text("", "left-border"); nextColumn()
    text("", "center-center-bold-border", Math.max(months.size(), 1), 1)
}

def printEndPeriodBalance(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = collectMonths(statusToTickets)

    nextRow()
    text("", "left-border"); nextColumn()
    text("Перечислено", "left-border"); nextColumn()
    text("", "center-border-format"); nextColumn()
    text("", "left-border"); nextColumn()
    text("", "center-center-bold-border", Math.max(months.size(), 1), 1)
    nextRow()
    text("", "left-border"); nextColumn()
    text("Сальдо на конец периода", "left-border"); nextColumn()
    formula("${cellIndex(-35, 0)}+${cellIndex(-2, 0)}-${cellIndex(-1, 0)}", "center-bold-border-format"); nextColumn()
    text("", "left-border"); nextColumn()
    text("", "center-center-bold-border", Math.max(months.size(), 1), 1)
}

def collectTickets() {
    Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets = new EnumMap<>(ProductStatus.class)
    statusToTickets.put(ProductStatus.SELL, new HashMap<>())
    statusToTickets.put(ProductStatus.EXCHANGE, new HashMap<>())
    statusToTickets.put(ProductStatus.REFUND, new HashMap<>())

    tickets { AirTicketsTemplateReportTicket ticket ->
        if (ticket.getType() != AirTicketsTemplateReportTicketType.MEMO
                && ticket.operationBatch != null
                && ticket.operationBatch != OperationBatch.VOID) {

            Map<Month, List<AirTicketsTemplateReportTicket>> monthToTickets
            switch (ticket.operationBatch) {
                case OperationBatch.SELL:
                    monthToTickets = statusToTickets.get(ProductStatus.SELL)
                    break
                case OperationBatch.REFUND:
                    monthToTickets = statusToTickets.get(ProductStatus.REFUND)
                    break
                case OperationBatch.EXCHANGE:
                    monthToTickets = statusToTickets.get(ProductStatus.EXCHANGE)
                    break
                default:
                    throw new IllegalArgumentException("Unsupported batch: " + ticket.operationBatch)
            }

            if (ticket.departureDate != null) {
                Month month
                if (ticket.departureDate != null) {
                    month = ticket.departureDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().getMonth()
                } else {
                    warn(String.format("У билета %s не найдена дата вылета", ticket.ticketNumber))
                    month = null
                }

                    List<AirTicketsTemplateReportTicket> ticketsList = monthToTickets.get(month)
                if (ticketsList == null) {
                    ticketsList = new ArrayList()
                    monthToTickets.put(month, ticketsList)
                }

                ticketsList.add(ticket)
            }
        }
    }

    return statusToTickets
}

static def collectMonths(Map<ProductStatus, Map<Month, List<AirTicketsTemplateReportTicket>>> statusToTickets) {
    Set<Month> months = EnumSet.noneOf(Month.class)

    statusToTickets.values().each {
        it.keySet().each {
            if (it != null) {
                months.add(it)
            }
        }
    }

    return months
}

static def getTariff(List<AirTicketsTemplateReportTicket> ticketsList) {
    BigDecimal res = BigDecimal.ZERO

    ticketsList.each { AirTicketsTemplateReportTicket ticket ->
        if (ticket.equivalentFare != null) {
            res = res.add(ticket.equivalentFare)
        }
    }

    return res
}

static def getTariffByPaymentTypes(List<AirTicketsTemplateReportTicket> ticketsList, PaymentType... paymentTypes) {
    BigDecimal res = BigDecimal.ZERO

    paymentTypes.each {
        res = res.add(getTariffByPaymentType(ticketsList, it))
    }

    return res
}

static def getTariffByPaymentType(List<AirTicketsTemplateReportTicket> ticketsList, PaymentType paymentType) {
    BigDecimal res = BigDecimal.ZERO

    ticketsList.each { AirTicketsTemplateReportTicket ticket ->
        if (ticket.paymentTypes.contains(paymentType)
                && ticket.equivalentFare != null) {
            res = res.add(ticket.equivalentFare)
        }
    }

    return res
}

static def getPenalty(List<AirTicketsTemplateReportTicket> ticketsList) {
    BigDecimal res = BigDecimal.ZERO

    ticketsList.each { AirTicketsTemplateReportTicket ticket ->
        if (ticket.penalty != null) {
            res = res.add(ticket.penalty)
        }
    }

    return res
}

static def getTaxesSum(List<AirTicketsTemplateReportTicket> ticketsList) {
    BigDecimal res = BigDecimal.ZERO

    ticketsList.each { AirTicketsTemplateReportTicket ticket ->
        if (ticket.taxesSum != null) {
            res = res.add(ticket.taxesSum)
        }
    }

    return res
}

static def getTicketsCount(Map<Month, List<AirTicketsTemplateReportTicket>> ticketsMap) {
    int res = 0

    ticketsMap.values().each { List<AirTicketsTemplateReportTicket> ticketList ->
        res += ticketList.size()
    }

    return res
}

static def getAllTickets(Map<Month, List<AirTicketsTemplateReportTicket>> ticketsMap) {
    List<AirTicketsTemplateReportTicket> res = new ArrayList<>()

    ticketsMap.values().each { List<AirTicketsTemplateReportTicket> ticketList ->
        res.addAll(ticketList)
    }

    return res
}

static def getVendorCommissionValue(List<AirTicketsTemplateReportTicket> ticketsList) {
    BigDecimal res = BigDecimal.ZERO

    ticketsList.each { AirTicketsTemplateReportTicket ticket ->
        if (ticket.vendorCommissionValue != null) {
            res = res.add(ticket.vendorCommissionValue)
        }
    }

    return res
}

def getAgencyMemoPenalties() {
    BigDecimal res = BigDecimal.ZERO

    tickets { AirTicketsTemplateReportTicket ticket ->
        if (ticket.type == AirTicketsTemplateReportTicketType.MEMO) {
            res = MiscUtil.sum(res,
                    ticket.agencyMemoProductTariffEquivalentAmount,
                    ticket.agencyMemoProductTaxesCarrierEquivalentAmount,
                    ticket.agencyMemoProductTaxesFuelEquivalentAmount,
                    ticket.agencyMemoProductTaxesStateEquivalentAmount,
                    ticket.agencyMemoProductTaxesOthersEquivalentAmount,
                    ticket.agencyMemoProductPenaltyEquivalentAmount,
                    ticket.agencyMemoProductContractPenaltyEquivalentAmount,
                    ticket.agencyMemoProductCommissionEquivalentAmount,
                    ticket.agencyMemoProductBonusEquivalentAmount,
                    ticket.agencyMemoProductFeeEquivalentAmount)
        }
    }

    return res
}

def setAutoWidth() {
    nextRow()

    3.times {
        columnAutoWidth()
        nextColumn()
    }

    columnWidth(10)
    nextColumn()

    12.times {
        columnAutoWidth()
        nextColumn()
    }
}

page{"Сводный отчет"} {
    // Set fix width for amount of sheets
    fitWidth(1)

    // Set fix height for amount of sheets
    fitHeight(1)

    // Set portrait mode
    landscape(false)

    // Set narrow margins
    margin(0.25, 0.25, 0.25, 0.25)

    // Set scale
    scale(70)

    // Set preserve mode
    preserve(false)

    printTable()
    setAutoWidth()
}