package com.ffii.fpsms.modules.report.web

import com.ffii.fpsms.modules.report.service.FGStockOutTraceabilityReportService
import com.ffii.fpsms.modules.report.service.ReportService
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.apache.poi.ss.usermodel.BorderStyle
import org.apache.poi.ss.usermodel.CellStyle
import org.apache.poi.ss.usermodel.DataFormat
import org.apache.poi.ss.usermodel.FillPatternType
import org.apache.poi.ss.usermodel.HorizontalAlignment
import org.apache.poi.ss.usermodel.IndexedColors
import org.apache.poi.ss.usermodel.Row
import org.apache.poi.ss.usermodel.VerticalAlignment
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.WorkbookUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.ByteArrayOutputStream
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter

@RestController
@RequestMapping("/report")
class FGStockOutTraceabilityReportController(
    private val reportService: ReportService,
    private val fgStockOutTraceabilityReportService: FGStockOutTraceabilityReportService,
) {
    @GetMapping("/fg-stock-out-traceability-handlers")
    fun getFGStockOutTraceabilityHandlers(): List<String> =
        fgStockOutTraceabilityReportService.getDistinctHandlersForFGStockOutTraceability()

    @GetMapping("/print-fg-stock-out-traceability")
    fun generateFGStockOutTraceabilityReport(
        @RequestParam(required = false) stockCategory: String?,
        @RequestParam(required = false) stockSubCategory: String?,
        @RequestParam(required = false) itemCode: String?,
        @RequestParam(required = false) year: String?,
        @RequestParam(required = false) lastOutDateStart: String?,
        @RequestParam(required = false) lastOutDateEnd: String?,
        @RequestParam(required = false) handler: String?,
    ): ResponseEntity<ByteArray> {
        val parameters = mutableMapOf<String, Any>()

        parameters["stockCategory"] = stockCategory ?: "All"
        parameters["stockSubCategory"] = stockSubCategory ?: "All"
        parameters["itemNo"] = itemCode ?: "All"
        parameters["year"] = year ?: LocalDate.now().year.toString()
        parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
        parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
        parameters["lastOutDateStart"] = lastOutDateStart ?: ""
        parameters["lastOutDateEnd"] = lastOutDateEnd ?: ""
        parameters["deliveryPeriodStart"] = ""
        parameters["deliveryPeriodEnd"] = ""

        val dbData = fgStockOutTraceabilityReportService.searchFGStockOutTraceabilityReport(
            stockCategory,
            stockSubCategory,
            itemCode,
            year,
            lastOutDateStart,
            lastOutDateEnd,
            handler,
        )

        val pdfBytes = reportService.createPdfResponse(
            "/jasper/FGStockOutTraceabilityReport.jrxml",
            parameters,
            dbData,
        )

        val headers = HttpHeaders().apply {
            contentType = MediaType.APPLICATION_PDF
            setContentDispositionFormData("attachment", "FGStockOutTraceabilityReport.pdf")
            set("filename", "FGStockOutTraceabilityReport.pdf")
        }

        return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
    }

    @GetMapping("/print-fg-stock-out-traceability-excel")
    fun exportFGStockOutTraceabilityReportExcel(
        @RequestParam(required = false) stockCategory: String?,
        @RequestParam(required = false) stockSubCategory: String?,
        @RequestParam(required = false) itemCode: String?,
        @RequestParam(required = false) year: String?,
        @RequestParam(required = false) lastOutDateStart: String?,
        @RequestParam(required = false) lastOutDateEnd: String?,
        @RequestParam(required = false) handler: String?,
    ): ResponseEntity<ByteArray> {
        val dbData = fgStockOutTraceabilityReportService.searchFGStockOutTraceabilityReport(
            stockCategory,
            stockSubCategory,
            itemCode,
            year,
            lastOutDateStart,
            lastOutDateEnd,
            handler,
        )

        val excelBytes = createFGStockOutTraceabilityExcel(dbData)

        val headers = HttpHeaders().apply {
            contentType = MediaType.parseMediaType(
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            )
            setContentDispositionFormData(
                "attachment",
                "FGStockOutTraceabilityReport.xlsx",
            )
            set("filename", "FGStockOutTraceabilityReport.xlsx")
        }

        return ResponseEntity(excelBytes, headers, HttpStatus.OK)
    }

    private fun createFGStockOutTraceabilityExcel(dbData: List<Map<String, Any>>): ByteArray {
        val workbook = XSSFWorkbook()
        val reportTitle = "成品出倉追蹤報告"
        val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle)
        val sheet = workbook.createSheet(safeSheetName)

        val totalColumns = 11
        var rowIndex = 0

        val headerStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.CENTER
            verticalAlignment = VerticalAlignment.CENTER
            fillForegroundColor = IndexedColors.GREY_25_PERCENT.index
            fillPattern = FillPatternType.SOLID_FOREGROUND
            borderTop = BorderStyle.THIN
            borderBottom = BorderStyle.THIN
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
            val font = workbook.createFont().apply { bold = true }
            setFont(font)
        }

        val textStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.LEFT
            verticalAlignment = VerticalAlignment.CENTER
            borderTop = BorderStyle.THIN
            borderBottom = BorderStyle.THIN
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
        }

        val numberStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.RIGHT
            verticalAlignment = VerticalAlignment.CENTER
            borderTop = BorderStyle.THIN
            borderBottom = BorderStyle.THIN
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
            val df: DataFormat = workbook.createDataFormat()
            dataFormat = df.getFormat("#,##0")
        }

        val dashStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.RIGHT
            verticalAlignment = VerticalAlignment.CENTER
            borderTop = BorderStyle.THIN
            borderBottom = BorderStyle.THIN
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
        }

        val headers = listOf(
            "貨品編號",
            "貨品名稱",
            "單位",
            "出貨單號",
            "提料單號",
            "批號",
            "到期日",
            "出貨數量",
            "提料人",
            "存貨位置",
            "提料備註",
        )

        val titleStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.CENTER
            verticalAlignment = VerticalAlignment.CENTER
            val font = workbook.createFont().apply {
                bold = true
                fontHeightInPoints = 14
            }
            setFont(font)
        }

        // Excel heading row
        val titleRow = sheet.createRow(rowIndex++)
        titleRow.createCell(0).apply {
            setCellValue(reportTitle)
            cellStyle = titleStyle
        }
        sheet.addMergedRegion(CellRangeAddress(0, 0, 0, totalColumns - 1))
        sheet.createRow(rowIndex++)

        val headerRowIndex = rowIndex
        val headerRow = sheet.createRow(rowIndex++)
        headers.forEachIndexed { i, h ->
            val cell = headerRow.createCell(i)
            cell.setCellValue(h)
            cell.cellStyle = headerStyle
        }

        val summaryQtyThickBottomStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.RIGHT
            verticalAlignment = VerticalAlignment.CENTER
            val df: DataFormat = workbook.createDataFormat()
            dataFormat = df.getFormat("#,##0")
            borderTop = BorderStyle.THICK
            borderBottom = BorderStyle.THICK
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
            val font = workbook.createFont().apply { bold = true }
            setFont(font)
        }

        val summaryEmptyThickBottomStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.LEFT
            verticalAlignment = VerticalAlignment.CENTER
            borderTop = BorderStyle.THICK
            borderBottom = BorderStyle.THICK
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
        }

        val summaryLabelInExpiryColStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.RIGHT
            verticalAlignment = VerticalAlignment.CENTER
            val font = workbook.createFont().apply { bold = true }
            setFont(font)
            borderTop = BorderStyle.THICK
            borderBottom = BorderStyle.THICK
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
        }

        val summaryHiddenKeyStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.LEFT
            verticalAlignment = VerticalAlignment.CENTER
            borderTop = BorderStyle.THICK
            borderBottom = BorderStyle.THICK
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
            // Keep value for Excel filtering/grouping while visually hiding text.
            val font = workbook.createFont().apply {
                color = IndexedColors.WHITE.index
            }
            setFont(font)
        }

        fun addItemSummaryRow(totalQty: Double, _itemCode: String, _itemName: String) {
            val summaryRow = sheet.createRow(rowIndex++)
            summaryRow.createCell(0).apply {
                setCellValue(_itemCode)
                cellStyle = summaryHiddenKeyStyle
            }
            summaryRow.createCell(1).apply {
                setCellValue(_itemName)
                cellStyle = summaryHiddenKeyStyle
            }
            for (col in 2..5) {
                summaryRow.createCell(col).apply {
                    setCellValue("")
                    cellStyle = summaryEmptyThickBottomStyle
                }
            }
            summaryRow.createCell(6).apply {
                setCellValue("總出倉量 :")
                cellStyle = summaryLabelInExpiryColStyle
            }
            summaryRow.createCell(7).apply {
                setCellValue(totalQty)
                cellStyle = summaryQtyThickBottomStyle
            }
            for (col in 8 until totalColumns) {
                summaryRow.createCell(col).apply {
                    setCellValue("")
                    cellStyle = summaryEmptyThickBottomStyle
                }
            }
        }

        fun addBlankSeparatorRow() {
            sheet.createRow(rowIndex++)
        }

        if (dbData.isEmpty()) {
            val dataRow = sheet.createRow(rowIndex++)
            for (col in 0 until totalColumns) {
                val cell = dataRow.createCell(col)
                cell.setCellValue("-")
                cell.cellStyle = textStyle
            }
            addItemSummaryRow(0.0, "", "")
        } else {
            var currentItemNo: String? = null
            var currentItemName = ""
            var currentItemTotalQty = 0.0

            dbData.forEach { rowMap ->
                val itemNo = rowMap["itemNo"]?.toString() ?: ""
                if (currentItemNo != null && itemNo != currentItemNo) {
                    addItemSummaryRow(currentItemTotalQty, currentItemNo!!, currentItemName)
                    addBlankSeparatorRow()
                    currentItemTotalQty = 0.0
                }

                val dataRow = sheet.createRow(rowIndex++)
                setTextCell(dataRow, 0, rowMap["itemNo"], textStyle)
                setTextCell(dataRow, 1, rowMap["itemName"], textStyle)
                setTextCell(dataRow, 2, rowMap["unitOfMeasure"], textStyle)
                setTextCell(dataRow, 3, rowMap["deliveryOrderNo"], textStyle)
                setTextCell(dataRow, 4, rowMap["stockReqNo"], textStyle)
                setTextCell(dataRow, 5, rowMap["lotNo"], textStyle)
                setTextCell(dataRow, 6, rowMap["expiryDate"], textStyle)
                setNumberCell(dataRow, 7, rowMap["stockOutQty"], numberStyle, dashStyle)
                setTextCell(dataRow, 8, rowMap["handler"], textStyle)
                setTextCell(dataRow, 9, rowMap["storeLocation"], textStyle)
                setTextCell(dataRow, 10, rowMap["pickRemark"], textStyle)

                currentItemNo = itemNo
                currentItemName = rowMap["itemName"]?.toString() ?: ""
                currentItemTotalQty += parseNullableNumber(rowMap["stockOutQty"]) ?: 0.0
            }

            addItemSummaryRow(currentItemTotalQty, currentItemNo ?: "", currentItemName)
        }

        val lastRowIndex = rowIndex - 1
        if (lastRowIndex >= headerRowIndex) {
            sheet.setAutoFilter(CellRangeAddress(headerRowIndex, lastRowIndex, 0, 0))
        }

        val widths = intArrayOf(18, 26, 10, 20, 18, 14, 14, 12, 18, 16, 18)
        widths.forEachIndexed { idx, w -> sheet.setColumnWidth(idx, w * 256) }

        val output = ByteArrayOutputStream()
        workbook.use { it.write(output) }
        return output.toByteArray()
    }

    private fun setTextCell(row: Row, col: Int, value: Any?, style: CellStyle) {
        val cell = row.createCell(col)
        cell.setCellValue(value?.toString() ?: "")
        cell.cellStyle = style
    }

    private fun parseNullableNumber(value: Any?): Double? {
        val s = value?.toString()?.replace(",", "")?.trim()
        if (s.isNullOrBlank()) return null
        if (s == "-" || s == "null") return null
        return s.toDoubleOrNull()
    }

    private fun setNumberCell(
        row: Row,
        col: Int,
        value: Any?,
        numberStyle: CellStyle,
        dashStyle: CellStyle,
    ) {
        val cell = row.createCell(col)
        val parsed = parseNullableNumber(value)
        if (parsed == null) {
            cell.setCellValue("-")
            cell.cellStyle = dashStyle
        } else {
            cell.setCellValue(parsed)
            cell.cellStyle = numberStyle
        }
    }
}
