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

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

@Service
class StockItemConsumptionTrendReportService(
    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 Stock Item Consumption Trend Report data.
     * Flow:
     * 1. Filter items by type (MAT, WIP) - this is the stockCategory filter
     * 2. Use items.code to match stock_ledger.itemCode
     * 3. Aggregate stock_ledger.outQty by month based on stock_ledger.modified
     * Supports comma-separated values for stockCategory, stockSubCategory, and itemCode.
     */
    fun searchStockItemConsumptionTrendReport(
        stockCategory: String?,
        itemCode: String?,
        year: String?,
        lastOutDateStart: String?,
        lastOutDateEnd: String?
    ): List<Map<String, Any>> {
        val args = mutableMapOf<String, Any>()
        
        // Filter by stockCategory from items.type (MAT/WIP/NM/FG/CMB)
        // Supports multiple categories separated by comma (e.g., "MAT,WIP,NM")
        // Valid categories: MAT, WIP, NM, FG, CMB
        // If "All" is selected or empty, show all valid categories
        // If specific categories are selected, show only those
        val validCategories = setOf("MAT", "WIP", "NM", "FG", "CMB")
        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" && validCategories.contains(it) }
            if (categories.isNotEmpty()) {
                val conditions = categories.mapIndexed { index, cat ->
                    val paramName = "stockCategory_$index"
                    args[paramName] = cat
                    "it.type = :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                // If no valid categories, default to all valid categories
                "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')"
            }
        } else {
            // If "All" or empty, filter to all valid categories
            "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')"
        }
        
        // itemCode now contains prefixes (first 2 letters), so we use LIKE to match codes starting with those prefixes
        val itemCodeSql = if (!itemCode.isNullOrBlank()) {
            val prefixes = itemCode.split(",").map { it.trim() }.filter { it.isNotBlank() }
            if (prefixes.isNotEmpty()) {
                val conditions = prefixes.mapIndexed { index, prefix ->
                    val paramName = "itemCode_$index"
                    args[paramName] = "$prefix%"
                    "it.code LIKE :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                ""
            }
        } else {
            ""
        }
        
        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(it.code, '') as itemNo,
                COALESCE(it.name, '') as itemName,
                COALESCE(uc.udfudesc, '') as unitOfMeasure,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 1 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJan,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 2 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyFeb,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 3 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyMar,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 4 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyApr,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 5 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyMay,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 6 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJun,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 7 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyJul,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 8 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyAug,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 9 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtySep,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 10 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyOct,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 11 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyNov,
                CAST(COALESCE(SUM(CASE WHEN MONTH(sl.modified) = 12 THEN COALESCE(sl.outQty, 0) ELSE 0 END), 0) AS DECIMAL(18,2)) as qtyDec,
                CAST(COALESCE(SUM(COALESCE(sl.outQty, 0)), 0) AS CHAR) as totalProductionQty
            FROM items it
            INNER JOIN stock_ledger sl ON it.code = sl.itemCode
            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 it.deleted = false
            AND sl.deleted = false
            AND sl.itemCode IS NOT NULL
            AND sl.itemCode != ''
            AND COALESCE(sl.outQty, 0) > 0
            $stockCategorySql
            $itemCodeSql
            $yearSql
            $lastOutDateStartSql
            $lastOutDateEndSql
            GROUP BY it.id, ic.sub, it.code, it.name, it.description, uc.udfudesc
            ORDER BY ic.sub, it.code
        """.trimIndent()
        
        return jdbcDao.queryForList(sql, args)
    }

    /**
     * Gets list of item code prefixes (first 2 letters) based on stockCategory filter.
     * Supports multiple categories separated by comma (e.g., "MAT,WIP,NM").
     * If stockCategory is "All" or null, returns prefixes for all valid categories (MAT, WIP, NM, FG, CMB).
     * Returns a list of maps with "prefix" key.
     */
    fun getStockItemCodePrefixes(stockCategory: String?): List<Map<String, String>> {
        val args = mutableMapOf<String, Any>()
        val validCategories = setOf("MAT", "WIP", "NM", "FG", "CMB")
        
        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" && validCategories.contains(it) }
            if (categories.isNotEmpty()) {
                val conditions = categories.mapIndexed { index, cat ->
                    val paramName = "stockCategory_$index"
                    args[paramName] = cat
                    "it.type = :$paramName"
                }
                "AND (${conditions.joinToString(" OR ")})"
            } else {
                "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')"
            }
        } else {
            "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')"
        }

        val sql = """
            SELECT DISTINCT LEFT(it.code, 2) as prefix
            FROM items it
            INNER JOIN stock_ledger sl ON it.code = sl.itemCode
            WHERE it.deleted = false
            AND sl.deleted = false
            AND sl.itemCode IS NOT NULL
            AND sl.itemCode != ''
            AND COALESCE(sl.outQty, 0) > 0
            AND LENGTH(it.code) >= 2
            $stockCategorySql
            ORDER BY prefix
        """.trimIndent()
        
        val results = jdbcDao.queryForList(sql, args)
        return results.mapNotNull { 
            val prefix = it["prefix"]?.toString()
            if (prefix != null && prefix.isNotBlank()) {
                mapOf("prefix" to prefix, "label" to prefix, "value" to prefix)
            } else {
                null
            }
        }
    }
}
