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

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

@Service
class SemiFGProductionAnalysisReportService(
    private val jdbcDao: JdbcDao,
) {
    /**
     * Helper function to build SQL clause for comma-separated values.
     * Supports multiple values like "val1, val2, val3" and generates OR conditions with LIKE.
     */
    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 ")})"
    }

    /**
     * Helper function to build SQL clause for comma-separated values with exact match.
     * Supports multiple values like "val1, val2, val3" and generates OR conditions with =.
     */
    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 ")})"
    }

    /**
     * Queries the database for Semi FG Production Analysis Report data.
     * Aligned with [ReportService.searchStockInTraceabilityReport] totals for the same filters:
     * - stock_in_line driven (no stock_ledger gate); INNER JOIN bom so only items that exist as BOM rows appear
     * - Include only stock_in_line rows with non-null jobOrderId
     * - Exclude stock_in_line rows with status = 'Pending'
     * - stockCategory → items.type (exact, comma-separated); itemCode → items.code (LIKE, comma-separated)
     * - Date path:
     *   stock_in_line.productLotNo (must be present) ->
     *   stock_in_line.inventoryLotId -> inventory_lot.id ->
     *   inventory_lot.stockInDate (used for month/year/date filters)
     * - Quantity source: stock_in_line.acceptedQty
     * - QC any fail → line qty 0 (same as traceability stockInQty)
     * - One row per stockInLineId per month before pivot; all lines counted (not only job orders)
     */
    fun searchSemiFGProductionAnalysisReport(
        stockCategory: String?,
        stockSubCategory: String?,
        itemCode: String?,
        year: String?,
        lastOutDateStart: String?,
        lastOutDateEnd: String?
    ): List<Map<String, Any>> {
        val args = mutableMapOf<String, Any>()

        val stockCategorySql = if (!itemCode.isNullOrBlank()) {
            ""
        } else if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) {
            buildMultiValueExactClause(stockCategory, "it.type", "semiSc", args)
        } else {
            ""
        }
        val stockSubCategorySql = buildMultiValueLikeClause(stockSubCategory, "ic.sub", "stockSubCategory", args)
        val itemCodeSql = buildMultiValueLikeClause(itemCode, "it.code", "semiItem", args)

        val yearSql = if (!year.isNullOrBlank() && year != "All") {
            args["year"] = year
            "AND YEAR(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) = :year"
        } else {
            ""
        }

        val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
            val formattedDate = lastOutDateStart.replace("/", "-")
            args["lastOutDateStart"] = formattedDate
            "AND DATE(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) >= DATE(:lastOutDateStart)"
        } else ""

        val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
            val formattedDate = lastOutDateEnd.replace("/", "-")
            args["lastOutDateEnd"] = formattedDate
            "AND DATE(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) <= DATE(:lastOutDateEnd)"
        } else ""

        val sql = """
            WITH qr_agg AS (
                SELECT
                    qr.stockInLineId,
                    MAX(CASE WHEN qr.qcPassed = 0 THEN 1 ELSE 0 END) AS qcFailed
                FROM qc_result qr
                WHERE qr.deleted = 0
                GROUP BY qr.stockInLineId
            ),
            base AS (
                SELECT
                    COALESCE(it.code, '') AS itemNo,
                    COALESCE(it.name, '') AS itemName,
                    COALESCE(ic.sub, '') AS stockSubCategory,
                    COALESCE(uc.udfudesc, '') AS unitOfMeasure,
                    MONTH(
                        CASE
                            WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate
                            ELSE si.productionDate
                        END
                    ) AS mon,
                    si.id AS stockInLineId,
                    CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN 0
                         ELSE COALESCE(si.acceptedQty, 0)
                    END AS linePutAwayQty
                FROM stock_in_line si
                INNER JOIN items it ON si.itemId = it.id
                INNER JOIN bom b ON b.code = it.code AND b.deleted = false
                LEFT JOIN inventory_lot il ON il.id = si.inventoryLotId AND il.deleted = false
                LEFT JOIN qr_agg ON qr_agg.stockInLineId = si.id
                LEFT JOIN item_category ic ON it.categoryId = ic.id
                LEFT JOIN item_uom iu ON it.id = iu.itemId AND iu.stockUnit = true
                LEFT JOIN uom_conversion uc ON iu.uomId = uc.id
                WHERE si.deleted = false
                  AND si.productLotNo IS NOT NULL
                  AND TRIM(si.productLotNo) <> ''
                  AND (
                      CASE
                          WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate
                          ELSE si.productionDate
                      END
                  ) IS NOT NULL
                  AND si.jobOrderId IS NOT NULL
                  AND (si.status IS NULL OR si.status <> 'Pending')
                  $stockCategorySql
                  $stockSubCategorySql
                  $itemCodeSql
                  $yearSql
                  $lastOutDateStartSql
                  $lastOutDateEndSql
            ),
            dedup AS (
                SELECT
                    itemNo,
                    itemName,
                    stockSubCategory,
                    unitOfMeasure,
                    mon,
                    stockInLineId,
                    MAX(linePutAwayQty) AS linePutAwayQty
                FROM base
                GROUP BY itemNo, itemName, stockSubCategory, unitOfMeasure, mon, stockInLineId
            )
            SELECT
                MAX(d.stockSubCategory) AS stockSubCategory,
                d.itemNo AS itemNo,
                MAX(d.itemName) AS itemName,
                MAX(d.unitOfMeasure) AS unitOfMeasure,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 1  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyJan,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 2  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyFeb,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 3  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyMar,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 4  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyApr,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 5  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyMay,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 6  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyJun,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 7  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyJul,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 8  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyAug,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 9  THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtySep,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 10 THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyOct,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 11 THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyNov,
                CAST(COALESCE(SUM(CASE WHEN d.mon = 12 THEN d.linePutAwayQty ELSE 0 END), 0) AS DECIMAL(18,2)) AS qtyDec,
                CAST(COALESCE(SUM(d.linePutAwayQty), 0) AS CHAR) AS totalProductionQty
            FROM dedup d
            GROUP BY d.itemNo
            HAVING COALESCE(SUM(d.linePutAwayQty), 0) > 0
            ORDER BY d.itemNo
        """.trimIndent()

        return jdbcDao.queryForList(sql, args)
    }

    /**
     * Gets list of item codes (bom.code) with names based on stockCategory filter.
     * Supports multiple categories separated by comma (e.g., "FG,WIP").
     * If stockCategory is "All" or null, returns all codes.
     * If stockCategory is "FG" or "WIP" or "FG,WIP", returns codes matching those descriptions.
     * Returns a list of maps with "code" and "name" keys.
     */
    fun getSemiFGItemCodes(stockCategory: String?): List<Map<String, String>> {
        val args = mutableMapOf<String, Any>()
        
        val stockCategorySql = if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) {
            buildMultiValueExactClause(stockCategory, "it.type", "semiFgCodesSc", args)
        } else {
            ""
        }

        val sql = """
            SELECT DISTINCT b.code, COALESCE(it.name, b.name, '') AS name
            FROM bom b
            INNER JOIN items it ON it.code = b.code AND it.deleted = false
            WHERE b.deleted = false
            AND b.code IS NOT NULL
            AND b.code != ''
            $stockCategorySql
            ORDER BY b.code
        """.trimIndent()
        
        val results = jdbcDao.queryForList(sql, args)
        return results.mapNotNull { 
            val code = it["code"]?.toString()
            val name = it["name"]?.toString() ?: ""
            if (code != null) {
                mapOf("code" to code, "name" to name)
            } else {
                null
            }
        }
    }

    /**
     * Gets list of item codes with their category (FG/WIP) and name based on stockCategory filter.
     * Supports multiple categories separated by comma (e.g., "FG,WIP").
     * Returns a list of maps with "code", "category", and "name" keys.
     */
    fun getSemiFGItemCodesWithCategory(stockCategory: String?): List<Map<String, String>> {
        val args = mutableMapOf<String, Any>()
        
        val stockCategorySql = if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) {
            buildMultiValueExactClause(stockCategory, "it.type", "semiFgCodesCatSc", args)
        } else {
            ""
        }

        val sql = """
            SELECT DISTINCT b.code, COALESCE(it.type, '') AS category, COALESCE(it.name, b.name, '') AS name
            FROM bom b
            INNER JOIN items it ON it.code = b.code AND it.deleted = false
            WHERE b.deleted = false
            AND b.code IS NOT NULL
            AND b.code != ''
            $stockCategorySql
            ORDER BY b.code
        """.trimIndent()
        
        val results = jdbcDao.queryForList(sql, args)
        return results.mapNotNull { 
            val code = it["code"]?.toString()
            val category = it["category"]?.toString() ?: ""
            val name = it["name"]?.toString() ?: ""
            if (code != null) {
                mapOf("code" to code, "category" to category, "name" to name)
            } else {
                null
            }
        }
    }
}
