package com.ffii.fpsms.modules.report.service

import com.ffii.core.support.JdbcDao
import org.springframework.stereotype.Service

@Service
open class ItemQcFailReportService(
    private val jdbcDao: JdbcDao,
) {
    fun searchItemQcFailReport(
        stockCategory: String?,
        itemCode: String?,
        lastInDateStart: String?,
        lastInDateEnd: String?,
        qcType: String?,
        includeMeasurable: String?,
        includeOther: String?,
        measurableScope: String?,
    ): List<Map<String, Any>> {
        val measurable = resolveIncludeMeasurable(includeMeasurable)
        val other = isTruthy(includeOther)
        val scope = resolveMeasurableScope(measurableScope, measurable, other)

        if (!measurable && !other) {
            return emptyList()
        }

        val args = mutableMapOf<String, Any>()

        val stockCategorySql = buildMultiValueExactClause(stockCategory, "it.type", "stockCategory", args)
        val itemCodeSql = buildMultiValueLikeClause(itemCode, "it.code", "itemCode", args)
        val qcTypeSql = buildQcTypeClause(qcType)
        val qcItemScopeSql = buildQcItemScopeClause(measurable, other)
        val measuredValueSql = buildMeasuredValueClause(measurable, scope)

        val lastInDateStartSql = if (!lastInDateStart.isNullOrBlank()) {
            val formattedDate = lastInDateStart.replace("/", "-")
            args["lastInDateStart"] = formattedDate
            "AND DATE(sil.receiptDate) >= DATE(:lastInDateStart)"
        } else ""

        val lastInDateEndSql = if (!lastInDateEnd.isNullOrBlank()) {
            val formattedDate = lastInDateEnd.replace("/", "-")
            args["lastInDateEnd"] = formattedDate
            "AND DATE(sil.receiptDate) <= DATE(:lastInDateEnd)"
        } else ""

        val sql = """
            SELECT
                COALESCE(it.type, '') AS stockSubCategory,
                COALESCE(it.code, '') AS itemNo,
                it.name AS itemName,
                COALESCE(uc.udfudesc, uc.code, '') AS unitOfMeasure,
                COALESCE(sil.lotNo, il.lotNo, '') AS lotNo,
                COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') AS expiryDate,

                CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END AS qcType,

                COALESCE(
                    qc.name,
                    (SELECT name
                    FROM qc_category
                    WHERE isDefault = 1
                    AND deleted = 0
                    LIMIT 1),
                    ''
                ) AS qcTemplate,

                COALESCE(qic.description, qi.description, qi.name, '') AS qcDefectCriteria,

                TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(
                  COALESCE(
                    CASE WHEN sil.purchaseOrderLineId IS NOT NULL
                         THEN sil.acceptedQty
                         WHEN iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL
                         THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0))
                         ELSE sil.acceptedQty END, 0), 2))) AS lotQty,
                TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr.failQty, 0), 2))) AS defectQty,

                CASE
                    WHEN qrm.value IS NOT NULL THEN
                        CONCAT(
                            TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(qrm.value, 2))),
                            COALESCE(qrm.unit, '')
                        )
                    ELSE ''
                END AS measuredValue,

                COALESCE(qr.remarks, '') AS remark,

                CASE
                    WHEN sil.jobOrderId IS NOT NULL THEN COALESCE(jo.code, '')
                    ELSE COALESCE(po.code, '')
                END AS orderRefNo,

                qr.qcPassed AS qcPassed

            FROM qc_result qr
            INNER JOIN stock_in_line sil
                ON qr.stockInLineId = sil.id
               AND sil.deleted = 0
            INNER JOIN items it
                ON qr.itemId = it.id
               AND it.deleted = 0
            LEFT JOIN qc_result_measurement qrm
                ON qrm.qcResultId = qr.id
               AND qrm.deleted = 0
            LEFT JOIN item_uom iu
                ON it.id = iu.itemId
               AND iu.stockUnit = 1
               AND iu.deleted = 0
            LEFT JOIN uom_conversion uc
                ON iu.uomId = uc.id
            LEFT JOIN item_uom iu_purchase
                ON it.id = iu_purchase.itemId
               AND iu_purchase.purchaseUnit = 1
               AND iu_purchase.deleted = 0
            LEFT JOIN inventory_lot il
                ON sil.inventoryLotId = il.id
               AND il.deleted = 0
            LEFT JOIN purchase_order po
                ON sil.purchaseOrderId = po.id
               AND po.deleted = 0
            LEFT JOIN job_order jo
                ON sil.jobOrderId = jo.id
               AND jo.deleted = 0

            LEFT JOIN items_qc_category_mapping iqcm
                ON iqcm.itemId = it.id
               AND iqcm.type = (CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END)
            LEFT JOIN qc_category qc
                ON qc.id = iqcm.qcCategoryId
               AND qc.deleted = 0

            LEFT JOIN qc_item qi
                ON qi.id = qr.qcItemId
               AND qi.deleted = 0
            LEFT JOIN qc_item_category qic
                ON qic.qcItemId = qi.id
               AND qic.qcCategoryId = COALESCE(
                    iqcm.qcCategoryId,
                    (SELECT id
                    FROM qc_category
                    WHERE isDefault = 1
                    AND deleted = 0
                    LIMIT 1)
                )

            WHERE
                qr.deleted = 0
                $qcItemScopeSql
                $measuredValueSql
                $qcTypeSql
                $stockCategorySql
                $itemCodeSql
                $lastInDateStartSql
                $lastInDateEndSql

            ORDER BY
                qr.qcPassed DESC,
                it.type,
                it.code,
                COALESCE(sil.lotNo, il.lotNo, ''),
                CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END,
                COALESCE(qic.`order`, 9999),
                COALESCE(qi.code, '')
        """.trimIndent()
        return jdbcDao.queryForList(sql, args)
    }

    private fun resolveIncludeMeasurable(includeMeasurable: String?): Boolean {
        return isTruthy(includeMeasurable)
    }

    private fun isTruthy(value: String?): Boolean {
        return value?.trim()?.lowercase() in listOf("true", "1", "yes")
    }

    private fun buildQcItemScopeClause(includeMeasurable: Boolean, includeOther: Boolean): String {
        val parts = mutableListOf<String>()
        if (includeMeasurable) {
            parts.add("qi.name IN ('溫度', '濕度')")
        }
        if (includeOther) {
            parts.add("(qi.name NOT IN ('溫度', '濕度') OR qi.name IS NULL)")
        }
        return "AND (${parts.joinToString(" OR ")})"
    }

    private fun resolveMeasurableScope(
        measurableScope: String?,
        includeMeasurable: Boolean,
        includeOther: Boolean,
    ): String {
        if (!measurableScope.isNullOrBlank()) {
            return measurableScope.trim().lowercase()
        }
        return if (includeMeasurable && !includeOther) "with" else "all"
    }

    /**
     * with = only temp/humidity rows with a filled measured value;
     * all = all temp/humidity rows;
     * without is handled by includeMeasurable=false on the API layer.
     */
    private fun buildMeasuredValueClause(includeMeasurable: Boolean, measurableScope: String): String {
        if (!includeMeasurable) return ""
        return when (measurableScope) {
            "with" -> "AND qrm.value IS NOT NULL"
            else -> ""
        }
    }

    private fun buildQcTypeClause(qcType: String?): String {
        return when (qcType?.trim()?.uppercase()) {
            "IQC" -> "AND sil.jobOrderId IS NULL"
            "EPQC" -> "AND sil.jobOrderId IS NOT NULL"
            else -> ""
        }
    }

    private fun buildMultiValueLikeClause(
        paramValue: String?,
        columnName: String,
        paramPrefix: String,
        args: MutableMap<String, Any>
    ): String {
        if (paramValue.isNullOrBlank()) return ""
        val values = paramValue.split(",").map { it.trim() }.filter { it.isNotBlank() }
        if (values.isEmpty()) return ""

        val conditions = values.mapIndexed { index, value ->
            val paramName = "${paramPrefix}_$index"
            args[paramName] = "%$value%"
            "$columnName LIKE :$paramName"
        }
        return "AND (${conditions.joinToString(" OR ")})"
    }

    private fun buildMultiValueExactClause(
        paramValue: String?,
        columnName: String,
        paramPrefix: String,
        args: MutableMap<String, Any>
    ): String {
        if (paramValue.isNullOrBlank()) return ""
        val values = paramValue.split(",").map { it.trim() }.filter { it.isNotBlank() }
        if (values.isEmpty()) return ""

        val conditions = values.mapIndexed { index, value ->
            val paramName = "${paramPrefix}_$index"
            args[paramName] = value
            "$columnName = :$paramName"
        }
        return "AND (${conditions.joinToString(" OR ")})"
    }
}
