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.
     * Flow:
     * 1. Filter bom by description (FG/WIP) to get bom.code values
     * 2. Match bom.code with stock_ledger.itemCode
     * 3. Aggregate stock_ledger data by month for each item based on inQty
     * Supports comma-separated values for stockCategory, stockSubCategory, and itemCode.
     */
    fun searchSemiFGProductionAnalysisReport(
        stockCategory: String?,
        stockSubCategory: String?,
        itemCode: String?,
        year: String?,
        lastOutDateStart: String?,
        lastOutDateEnd: String?
    ): List<Map<String, Any>> {
        val args = mutableMapOf<String, Any>()
        
        // Filter by stockCategory from bom.description (FG/WIP) - this finds which bom.code values match
        // Supports multiple categories separated by comma (e.g., "FG,WIP")
        // If "All" is selected or contains "All", don't filter by description
        val stockCategorySql = if (!itemCode.isNullOrBlank()) {
            // When itemCode is provided, skip stockCategory filter
            ""
        } else if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) {
            // Handle multiple categories (comma-separated)
            val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() && it != "All" }
            if (categories.isNotEmpty()) {
                val conditions = categories.mapIndexed { index, cat ->
                    val paramName = "stockCategory_$index"
                    args[paramName] = cat
                    "b.description = :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                ""
            }
        } else {
            ""
        }
        val stockSubCategorySql = buildMultiValueLikeClause(stockSubCategory, "ic.sub", "stockSubCategory", args)
        // Filter by itemCode - match bom.code (user input should match bom.code, which then matches stock_ledger.itemCode)
        val itemCodeSql = buildMultiValueExactClause(itemCode, "b.code", "itemCode", args)
        
        val yearSql = if (!year.isNullOrBlank()) {
            args["year"] = year
            "AND YEAR(sl.modified) = :year"
        } else {
            ""
        }
        
        val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
            val formattedDate = lastOutDateStart.replace("/", "-")
            args["lastOutDateStart"] = formattedDate
            "AND DATE(sl.modified) >= DATE(:lastOutDateStart)"
        } else ""
        
        val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
            val formattedDate = lastOutDateEnd.replace("/", "-")
            args["lastOutDateEnd"] = formattedDate
            "AND DATE(sl.modified) <= DATE(:lastOutDateEnd)"
        } else ""

        val sql = """
            SELECT 
                COALESCE(ic.sub, '') as stockSubCategory,
                COALESCE(sl.itemCode, '') as itemNo,
                COALESCE(b.name, '') as itemName,
                COALESCE(uc.udfudesc, '') as unitOfMeasure,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 1 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJan,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 2 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyFeb,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 3 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyMar,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 4 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyApr,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 5 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyMay,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 6 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJun,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 7 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJul,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 8 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyAug,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 9 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtySep,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 10 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyOct,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 11 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyNov,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 12 THEN sl.inQty ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyDec,
                CAST(COALESCE(SUM(sl.inQty), 0) AS CHAR) as totalProductionQty
            FROM stock_ledger sl
            INNER JOIN bom b ON sl.itemCode = b.code AND b.deleted = false
            LEFT JOIN items it ON sl.itemId = it.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 sl.deleted = false
            AND sl.inQty IS NOT NULL
            AND sl.inQty > 0
            $stockCategorySql
            $stockSubCategorySql
            $itemCodeSql
            $yearSql
            $lastOutDateStartSql
            $lastOutDateEndSql
            GROUP BY sl.itemCode, ic.sub, it.id, b.name, uc.udfudesc, b.description
            ORDER BY ic.sub, sl.itemCode
        """.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")) {
            // Handle multiple categories (comma-separated)
            val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() && it != "All" }
            if (categories.isNotEmpty()) {
                val conditions = categories.mapIndexed { index, cat ->
                    val paramName = "stockCategory_$index"
                    args[paramName] = cat
                    "b.description = :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                ""
            }
        } else {
            ""
        }

        val sql = """
            SELECT DISTINCT b.code, COALESCE(b.name, '') as name
            FROM bom b
            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")) {
            // Handle multiple categories (comma-separated)
            val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() && it != "All" }
            if (categories.isNotEmpty()) {
                val conditions = categories.mapIndexed { index, cat ->
                    val paramName = "stockCategory_$index"
                    args[paramName] = cat
                    "b.description = :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                ""
            }
        } else {
            ""
        }

        val sql = """
            SELECT DISTINCT b.code, COALESCE(b.description, '') as category, COALESCE(b.name, '') as name
            FROM bom b
            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
            }
        }
    }
}
