package com.ffii.fpsms.modules.master.service

import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.web.models.ItemQc
import com.ffii.fpsms.modules.master.web.models.ItemWithQcResponse
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.master.web.models.NewItemRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
import kotlin.jvm.optionals.getOrNull

@Service
open class ItemsService(
    private val jdbcDao: JdbcDao,
    private val itemsRepository: ItemsRepository,
    private val qcCheckRepository: QcCheckRepository,
    private val qcItemsRepository: QcItemRepository, private val qcCategoryRepository: QcCategoryRepository,
): AbstractBaseEntityService<Items, Long, ItemsRepository>(jdbcDao, itemsRepository)  {
    // do mapping with projection
    open fun allItems(): List<Items> {
        // TODO: Replace by actual logic
        val items = itemsRepository.findAll()
        return items
    }

    open fun allConsumables(): List<Map<String, Any>> {
        val args = mapOf(
            "type" to "mat"
        )
        val sql = StringBuilder("select"
            + " i.id, "
            + " concat(i.code , ' - ' , i.name) as label, "
            + " uc.id as uomId, "
            + " uc.code as uom, "
            + " uc.udfudesc as uomDesc, "
            + " COALESCE(inv.availableQty, 0) as currentStockBalance "
            + " from items i "
            + " left join item_uom iu on iu.itemId = i.id and iu.deleted = false and iu.salesUnit = true "
            + " left join uom_conversion uc on uc.id = iu.uomId "
            + " left join ("
            + "   SELECT "
            + "     il.itemId, "
            + "     SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty "
            + "   FROM inventory_lot_line ill "
            + "   JOIN inventory_lot il ON il.id = ill.inventoryLotId "
            + "   LEFT JOIN item_uom iu_ratio ON iu_ratio.itemId = il.itemId AND iu_ratio.deleted = false AND iu_ratio.salesUnit = true "
            + "   WHERE ill.status = 'available' "
            + "   AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 "
            + "   AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE()) "
            + "   GROUP BY il.itemId "
            + " ) inv ON inv.itemId = i.id " 
            + " where i.deleted = false "
            + " and i.type = :type "
        )

        return jdbcDao.queryForList(sql.toString(), args);
    }
    open fun getPickOrderItemsByPage(args: Map<String, Any>): List<Map<String, Any>> {
        try {
            println("=== Debug: getPickOrderItemsByPage in ItemsService ===")
            println("Args: $args")
            
            val sql = StringBuilder(
                """
                SELECT 
                    CONCAT(po.id, '-', pol.itemId) as id,
                    po.id as pickOrderId,
                    po.code as pickOrderCode,
                    pol.itemId,
                    i.code as itemCode,
                    i.name as itemName,
                    pol.qty as requiredQty,
                    COALESCE(inv.availableQty, 0) as currentStock,
                    COALESCE(uc.udfudesc, 'N/A') as unit,
                    DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate,
                    po.status,
                    po.consoCode,
                    po.assignTo,
                    COALESCE(pog.name, 'No Group') as groupName
                FROM pick_order po
                JOIN pick_order_line pol ON pol.poId = po.id
                JOIN items i ON i.id = pol.itemId
                LEFT JOIN uom_conversion uc ON uc.id = pol.uomId
                LEFT JOIN pick_order_group pog ON pog.pick_order_id = po.id AND pog.deleted = false
                LEFT JOIN (
                    SELECT 
                        il.itemId,
                        SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty
                    FROM inventory_lot_line ill
                    JOIN inventory_lot il ON il.id = ill.inventoryLotId
                    LEFT JOIN item_uom iu_ratio ON iu_ratio.itemId = il.itemId AND iu_ratio.deleted = false AND iu_ratio.salesUnit = true
                    WHERE ill.status = 'available'
                    AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0
                    AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE())
                    GROUP BY il.itemId
                ) inv ON inv.itemId = pol.itemId
                WHERE po.deleted = false
                AND po.consoCode IS NULL
                """
            )
            
            if (args.containsKey("itemCode")) {
                sql.append(" AND i.code LIKE :itemCode ")
            }
            
            if (args.containsKey("itemName")) {
                sql.append(" AND i.name LIKE :itemName ")
            }
            
            if (args.containsKey("status")) {
                sql.append(" AND po.status = :status ")
            }
            
            if (args.containsKey("targetDateFrom")) {
                sql.append(" AND po.targetDate >= :targetDateFrom ")
            }
            
            if (args.containsKey("targetDateTo")) {
                sql.append(" AND po.targetDate <= :targetDateTo ")
            }
            
            sql.append(" ORDER BY po.targetDate DESC, i.name ASC ")
            
            val finalSql = sql.toString()
            println("Final SQL: $finalSql")
            println("SQL args: $args")
            
            val result = jdbcDao.queryForList(finalSql, args)
            println("Query result size: ${result.size}")
            result.forEach { row -> println("Result row: $row") }
            return result
        } catch (e: Exception) {
            println("Error in getPickOrderItemsByPage: ${e.message}")
            e.printStackTrace()
            throw e
        }
    }
    
    open fun getRoughScheduleList(): List<Map<String, Any>> {
        val now = LocalDateTime.now()
        val lastMonthStart = now.minusMonths(1).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0) // Start of last month
        val lastMonthEnd = now.minusDays(now.dayOfMonth.toLong()).withHour(23).withMinute(59).withSecond(59) // End of last month

        val curMonthStart = now.withDayOfMonth(1) // Start of last month
        val curMonthEnd = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59) // End of last month

        // Format dates if needed
        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

        val args = mapOf(
            "lastMonthStart" to lastMonthStart.format(formatter),
            "lastMonthEnd" to lastMonthEnd.format(formatter)
        )

        println("lastMonthStart:" + args.get("lastMonthStart"))
        println("lastMonthEnd:" + args.get("lastMonthEnd"))

        val sql = StringBuilder(
            " SELECT "
            + " dol.itemId, "
            + " i.name AS itemName, "
            + " dol.itemNo, "
            + " 0 AS openBalance, "
            + " COUNT(DISTINCT dol.deliveryOrderId) AS orderCount, "
            + " SUM(IFNULL(dol.qty, 0)) AS totalQty, "
            + " SUM(IFNULL(dol.qty, 0)) / COUNT(DISTINCT dol.deliveryOrderId) AS lastMonthAvgSalesCount, "
            + " 22000 - IFNULL(i.maxQty , 0) AS fgProductionLimit "
            + " FROM delivery_order do "
            + " LEFT JOIN delivery_order_line dol ON dol.deliveryOrderId = do.id "
            + " LEFT JOIN items i ON i.id = dol.itemId "
            + " WHERE do.deleted = false "
            + " AND do.estimatedArrivalDate >= :lastMonthStart "
            + " AND do.estimatedArrivalDate <= :lastMonthEnd "
            + " AND dol.itemId is not null "
            + " GROUP BY dol.itemId, dol.itemNo, i.name "
        );
        return jdbcDao.queryForList(sql.toString(), args);
    }

    open fun getItemsByPage(args: Map<String, Any>): List<Map<String, Any>>  {
        // Extract parameters from the DTO
        val sql = StringBuilder(
            "SELECT " +
            "i.id, " +
            "i.code, " +
            "i.name, " +
            "i.description " +
            "FROM items i " +
            "WHERE i.deleted = FALSE"
        );

        if (args.containsKey("name")){
            sql.append(" AND i.name like :name ");
        }

        if (args.containsKey("code")){
            sql.append(" AND i.code like :code ");
        }

        return jdbcDao.queryForList(sql.toString(), args);
    }

    open fun findById(id: Long): Items? {
        return itemsRepository.findByIdAndDeletedFalse(id);
    }

    open fun findByCode(code: String): Items? {
        return itemsRepository.findByCodeAndDeletedFalse(code);
    }

    open fun findByM18Id(m18Id: Long): Items? {
        return itemsRepository.findByM18IdAndDeletedIsFalse(m18Id)
    }

    open fun findByNameLikeAndM18UomId(name: String, m18UomId: Long): Items? {
        return itemsRepository.findByNameLikeAndUomM18Id(name, m18UomId);
    }

    open fun findByNameExactlyAndM18UomId(name: String, m18UomId: Long): Items? {
        return itemsRepository.findByNameExactlyAndUomM18Id(name, m18UomId);
    }

    open fun findByNameAndM18UomId(name: String, m18UomId: Long): Items? {
        return findByNameExactlyAndM18UomId(name, m18UomId) ?: findByNameLikeAndM18UomId(name, m18UomId);
    }

    open fun findByM18BomCode(m18BomCode: String): Items? {
        return itemsRepository.findByM18BomCode(m18BomCode);
    }

    // QcCheck included item
    open fun getItem(id: Long): ItemWithQcResponse {
        val list = listOf(1,2)
        val item = itemsRepository.findByIdAndDeletedFalse(id) ?: Items()
        val qcItems = qcItemsRepository.findAllByDeletedIsFalse()
        val qcCheckMap = qcCheckRepository.findAllByItemIdAndDeletedFalse(id).associateBy { it.id }
        val qc = qcItems.map {
            val check = qcCheckMap[it.id] // Find the corresponding qcCheck
            println(check?.description)
            println(check?.lowerLimit)
            ItemQc(
                id = it.id!!,
                name = it.name!!,
                code = it.code!!,
                description = it.description!!,
                instruction = check?.description,
                lowerLimit = check?.lowerLimit ,
                upperLimit = check?.upperLimit,
                isActive = check != null
                ) // Create combined item
        }
        val response = ItemWithQcResponse(
            item = item,
            qcChecks = qc
        )
        // TODO: Return with QC items
        return response
    }
//    open fun getItem(id: Long): Items? {
//        // TODO: Return with QC items
//        return itemsRepository.findById(id).get()
//    }
    @Throws(IOException::class)
    @Transactional
    open fun saveItem(request: NewItemRequest): MessageResponse {
        val duplicatedItem = itemsRepository.findByCodeAndTypeAndDeletedFalse(request.code, request.type)
        if (duplicatedItem != null && duplicatedItem.id != request.id) {
            return MessageResponse(
                id = request.id,
                code = request.code,
                name = request.name,
                type = request.type.toString(),
                message = "The item code has already existed",
                errorPosition = "code"
            )
        }
        val item = if (request.m18Id != null) findByM18Id(request.m18Id) ?: Items()
            else if (request.id != null && request.id > 0) itemsRepository.findByIdAndDeletedFalse(request.id) ?: Items()
            else Items()
        logger.info("item: $item")
        if (item.m18LastModifyDate == request.m18LastModifyDate) {
            return MessageResponse(
                id = item.id,
                code = item.code,
                name = item.name,
                type = item.type.toString(),
                message = "M18 Item does not have any updates",
                errorPosition = "m18LastModifyDate"
            )
        }

        val qcCategory = request.qcCategoryId?.let { qcCategoryRepository.findById(it).getOrNull() }

        item.apply {
            code = request.code
            name = request.name
            description = request.description
            remarks = request.remarks
            shelfLife = request.shelfLife
            countryOfOrigin = request.countryOfOrigin
            maxQty = request.maxQty
            this.type = request.type
            this.qcCategory = qcCategory
            m18Id = request.m18Id ?: this.m18Id
            m18LastModifyDate = request.m18LastModifyDate ?: this.m18LastModifyDate
        }
        logger.info("saving item: $item")
        val savedItem = itemsRepository.saveAndFlush(item)
        logger.info("save success")
        return MessageResponse(
            id = savedItem.id,
            name = savedItem.name,
            code = savedItem.code,
            type = savedItem.type.toString(),
            message = "Item Save Success",
            errorPosition = null,
        )
    }

    open fun getItemsWithDetailsByPage(args: Map<String, Any>): List<Map<String, Any>> {
        val sql = StringBuilder("select"
            + " i.id, "
            + " i.code, "
            + " i.name, "
            + " i.type, "
            + " i.description, "
            + " uc.id as uomId, "
            + " uc.code as uom, "
            + " uc.udfudesc as uomDesc, "
            + " COALESCE(inv.availableQty, 0) as currentStockBalance "
            + " from items i "
            + " left join item_uom iu on iu.itemId = i.id and iu.deleted = false and iu.salesUnit = true "
            + " left join uom_conversion uc on uc.id = iu.uomId "
            + " left join ("
            + "   SELECT "
            + "     il.itemId, "
            + "     SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty "
            + "   FROM inventory_lot_line ill "
            + "   JOIN inventory_lot il ON il.id = ill.inventoryLotId "
            + "   LEFT JOIN item_uom iu_ratio ON iu_ratio.itemId = il.itemId AND iu_ratio.deleted = false AND iu_ratio.salesUnit = true "
            + "   WHERE ill.status = 'available' "
            + "   AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 "
            + "   AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE()) "
            + "   GROUP BY il.itemId "
            + " ) inv ON inv.itemId = i.id " 
            + " where i.deleted = false "
        )
    
        // Add search conditions
        if (args.containsKey("name")) {
            sql.append(" AND i.name LIKE :name ")
        }
        
        if (args.containsKey("code")) {
            sql.append(" AND i.code LIKE :code ")
        }
        
        if (args.containsKey("description")) {
            sql.append(" AND i.description LIKE :description ")
        }
        
        if (args.containsKey("type")) {
            sql.append(" AND i.type = :type ")
        }
    
        sql.append(" ORDER BY i.name ASC ")
    
        return jdbcDao.queryForList(sql.toString(), args);
    }

    open fun getItemsIdWithSameCategoryForQc(args: Map<String, Any>): List<Int> {
        val sql = StringBuilder("SELECT"
                + " i.id "
                + " FROM items i "
                + " INNER JOIN items_qc_category_mapping iqcm ON iqcm.itemId = i.id AND iqcm.type = :qcType "
                + " WHERE i.deleted = false "
                + " AND LEFT(i.code, 2) = (SELECT LEFT(code, 2) FROM items WHERE id = :itemId)"
                + " AND i.id != :itemId "
        )
        return jdbcDao.queryForInts(sql.toString(), args);
    }
}