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

import org.springframework.http.*
import org.springframework.web.bind.annotation.*
import com.ffii.fpsms.modules.report.service.ItemQcFailReportService
import com.ffii.fpsms.modules.report.service.ReportService
import org.apache.poi.ss.usermodel.BorderStyle
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.VerticalAlignment
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.ss.util.WorkbookUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook

@RestController
@RequestMapping("/report")
class ItemQcFailReportController(

    private val reportService: ReportService,
    private val itemQcFailReportService: ItemQcFailReportService,
) {

    @GetMapping("/print-item-qc-fail")
    fun generateItemQcFailReport(
        @RequestParam(required = false) stockCategory: String?,
        @RequestParam(required = false) itemCode: String?,
        @RequestParam(required = false) lastInDateStart: String?,
        @RequestParam(required = false) lastInDateEnd: String?,
        @RequestParam(required = false) qcType: String?,
        @RequestParam(required = false, defaultValue = "true") includeMeasurable: String?,
        @RequestParam(required = false, defaultValue = "false") includeOther: String?,
        @RequestParam(required = false, defaultValue = "all") measurableScope: String?,
    ): ResponseEntity<ByteArray> {
        val dbData = itemQcFailReportService.searchItemQcFailReport(
            stockCategory = stockCategory,
            itemCode = itemCode,
            lastInDateStart = lastInDateStart,
            lastInDateEnd = lastInDateEnd,
            qcType = qcType,
            includeMeasurable = includeMeasurable,
            includeOther = includeOther,
            measurableScope = measurableScope,
        )

        val passedRows = dbData.filter { isQcPassed(it) }
        val failedRows = dbData.filter { !isQcPassed(it) }

        val pdfParts = mutableListOf<ByteArray>()
        if (passedRows.isNotEmpty()) {
            pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, passedRows, "合格"))
        }
        if (failedRows.isNotEmpty()) {
            pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, failedRows, "不合格"))
        }

        val pdfBytes = when {
            pdfParts.isEmpty() -> createItemQcPdf(
                stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, emptyList(), "合格"
            )
            pdfParts.size == 1 -> pdfParts[0]
            else -> reportService.mergePdfBytes(pdfParts)
        }

        val headers = HttpHeaders().apply {
            contentType = MediaType.APPLICATION_PDF
            setContentDispositionFormData("attachment", "ItemQCReport.pdf")
            set("filename", "ItemQCReport.pdf")
        }
        return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
    }

    @GetMapping("/print-item-qc-fail-excel")
    fun exportItemQcFailReportExcel(
        @RequestParam(required = false) stockCategory: String?,
        @RequestParam(required = false) itemCode: String?,
        @RequestParam(required = false) lastInDateStart: String?,
        @RequestParam(required = false) lastInDateEnd: String?,
        @RequestParam(required = false) qcType: String?,
        @RequestParam(required = false, defaultValue = "true") includeMeasurable: String?,
        @RequestParam(required = false, defaultValue = "false") includeOther: String?,
        @RequestParam(required = false, defaultValue = "all") measurableScope: String?,
    ): ResponseEntity<ByteArray> {
        val dbData = itemQcFailReportService.searchItemQcFailReport(
            stockCategory = stockCategory,
            itemCode = itemCode,
            lastInDateStart = lastInDateStart,
            lastInDateEnd = lastInDateEnd,
            qcType = qcType,
            includeMeasurable = includeMeasurable,
            includeOther = includeOther,
            measurableScope = measurableScope,
        )

        val passedRows = dbData.filter { isQcPassed(it) }
        val failedRows = dbData.filter { !isQcPassed(it) }

        val excelBytes = createItemQcMeasurementExcel(
            passedRows = passedRows,
            failedRows = failedRows,
            lastInDateStart = lastInDateStart,
            lastInDateEnd = lastInDateEnd,
            qcType = qcType,
        )

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

    private fun createItemQcPdf(
        stockCategory: String?,
        itemCode: String?,
        lastInDateStart: String?,
        lastInDateEnd: String?,
        qcType: String?,
        rows: List<Map<String, Any>>,
        reportSection: String,
    ): ByteArray {
        val parameters = mutableMapOf<String, Any>()
        parameters["stockCategory"] = stockCategory ?: "All"
        parameters["stockSubCategory"] = stockCategory ?: "All"
        parameters["itemNo"] = itemCode ?: "All"
        parameters["year"] = java.time.LocalDate.now().year.toString()
        parameters["reportDate"] = java.time.LocalDate.now()
            .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"))
        parameters["reportTime"] = java.time.LocalTime.now()
            .format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"))
        parameters["storeLocation"] = ""
        parameters["balanceFilterStart"] = ""
        parameters["balanceFilterEnd"] = ""
        parameters["lastInDateStart"] = lastInDateStart ?: ""
        parameters["lastInDateEnd"] = lastInDateEnd ?: ""
        parameters["lastOutDateStart"] = ""
        parameters["lastOutDateEnd"] = ""
        parameters["reportSection"] = reportSection
        parameters["qcTypeLabel"] = resolveQcTypeLabel(qcType)

        return reportService.createPdfResponse(
            "/jasper/ItemQCReport.jrxml",
            parameters,
            rows,
        )
    }

    private fun resolveQcTypeLabel(qcType: String?): String {
        return when (qcType?.trim()?.uppercase()) {
            "IQC" -> "IQC"
            "EPQC" -> "EPQC"
            else -> "全部"
        }
    }

    private fun isQcPassed(row: Map<String, Any>): Boolean {
        return when (val value = row["qcPassed"]) {
            is Boolean -> value
            is Number -> value.toInt() == 1
            else -> value?.toString() in listOf("1", "true", "TRUE")
        }
    }

    private fun createItemQcMeasurementExcel(
        passedRows: List<Map<String, Any>>,
        failedRows: List<Map<String, Any>>,
        lastInDateStart: String?,
        lastInDateEnd: String?,
        qcType: String?,
    ): ByteArray {
        val workbook: Workbook = XSSFWorkbook()
        writeQcSheet(workbook, "合格", passedRows, lastInDateStart, lastInDateEnd, qcType)
        writeQcSheet(workbook, "不合格", failedRows, lastInDateStart, lastInDateEnd, qcType)
        return workbookToByteArray(workbook)
    }

    private fun writeQcSheet(
        workbook: Workbook,
        sheetLabel: String,
        dbData: List<Map<String, Any>>,
        lastInDateStart: String?,
        lastInDateEnd: String?,
        qcType: String?,
    ) {
        val reportTitle = "庫存品質檢測報告"
        val safeSheetName = WorkbookUtil.createSafeSheetName(sheetLabel)
        val sheet = workbook.createSheet(safeSheetName)

        var rowIndex = 0
        val columnCount = 14

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

        run {
            val titleRowIndex = rowIndex++
            val row = sheet.createRow(titleRowIndex)
            val cell = row.createCell(0)
            cell.setCellValue("$reportTitle ($sheetLabel)")
            cell.cellStyle = titleStyle
            sheet.addMergedRegion(
                org.apache.poi.ss.util.CellRangeAddress(
                    titleRowIndex,
                    titleRowIndex,
                    0,
                    columnCount - 1
                )
            )
            row.heightInPoints = 24f
        }

        run {
            val row = sheet.createRow(rowIndex++)
            val cell = row.createCell(0)
            val startTxt = lastInDateStart ?: ""
            val endTxt = lastInDateEnd ?: ""
            val qcTypeTxt = resolveQcTypeLabel(qcType)
            cell.setCellValue("QC 檢測日期: ${startTxt} - ${endTxt}    QC 類型: $qcTypeTxt")
        }

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

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

        val wrappedTextStyle = workbook.createCellStyle().apply {
            alignment = HorizontalAlignment.LEFT
            verticalAlignment = VerticalAlignment.TOP
            borderTop = BorderStyle.THIN
            borderBottom = BorderStyle.THIN
            borderLeft = BorderStyle.THIN
            borderRight = BorderStyle.THIN
            wrapText = true
        }

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

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

        val headers = listOf(
            "庫存類別",
            "物料編號",
            "物料名稱",
            "單位",
            "批號",
            "到期日",
            "QC 類型",
            "QC 範本",
            "檢測標準",
            "批量",
            "不合格數量",
            "實測值",
            "備註",
            "訂單/工單"
        )

        run {
            val row = sheet.createRow(rowIndex++)
            headers.forEachIndexed { col, h ->
                val cell = row.createCell(col)
                cell.setCellValue(h)
                cell.cellStyle = headerStyle
            }
            row.heightInPoints = 35f
        }

        dbData.forEach { r ->
            val row = sheet.createRow(rowIndex++)

            fun writeText(col: Int, value: Any?) {
                val cell = row.createCell(col)
                cell.setCellValue(value?.toString() ?: "")
                cell.cellStyle = textStyle
            }

            fun writeNumber(col: Int, value: Any?) {
                val raw = value?.toString()?.trim() ?: ""
                val cleaned = raw.removeSuffix(".")
                val bd = cleaned.toBigDecimalOrNull()
                val cell = row.createCell(col)

                if (bd == null) {
                    cell.setCellValue(cleaned)
                    cell.cellStyle = textStyle
                } else {
                    val stripped = bd.stripTrailingZeros()
                    if (stripped.scale() <= 0) {
                        cell.setCellValue(stripped.toDouble())
                        cell.cellStyle = integerNumberStyle
                    } else {
                        cell.setCellValue(stripped.toDouble())
                        cell.cellStyle = decimalNumberStyle
                    }
                }
            }

            writeText(0, r["stockSubCategory"])
            writeText(1, r["itemNo"])
            writeText(2, r["itemName"])
            writeText(3, r["unitOfMeasure"])
            writeText(4, r["lotNo"])
            writeText(5, r["expiryDate"])
            writeText(6, r["qcType"])
            writeText(7, r["qcTemplate"])
            val defectCriteria = r["qcDefectCriteria"]
            run {
                val cell = row.createCell(8)
                cell.setCellValue(defectCriteria?.toString() ?: "")
                cell.cellStyle = wrappedTextStyle
            }
            writeNumber(9, r["lotQty"])
            writeNumber(10, r["defectQty"])
            writeText(11, r["measuredValue"])
            writeText(12, r["remark"])
            writeText(13, r["orderRefNo"])

            val defectText = defectCriteria?.toString() ?: ""
            val charsPerLine = 38.0
            val lines = maxOf(
                1,
                kotlin.math.ceil(defectText.length / charsPerLine).toInt()
            )
            val baseHeight = 18f
            val lineHeight = 13.5f
            row.heightInPoints = (baseHeight + (lines - 1) * lineHeight).coerceIn(18f, 110f)
        }

        val widths = intArrayOf(12, 16, 18, 10, 14, 12, 10, 20, 42, 12, 14, 14, 16, 18)
        for (col in widths.indices) {
            sheet.setColumnWidth(col, widths[col] * 256)
        }
    }

    private fun workbookToByteArray(workbook: Workbook): ByteArray {
        val out = java.io.ByteArrayOutputStream()
        workbook.write(out)
        workbook.close()
        return out.toByteArray()
    }
}
