package com.ffii.fpsms.modules.dashboard.service

import com.ffii.fpsms.modules.dashboard.web.models.GoodsReceiptStatusResponse
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus
import com.ffii.fpsms.modules.qc.entity.QcResultRepository
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class GoodsReceiptStatusService(
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val purchaseOrderLineRepository: PurchaseOrderLineRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val qcResultRepository: QcResultRepository
) {
    private data class Agg(
        val purchaseOrderId: Long?,
        val purchaseOrderCode: String?,
        val supplierId: Long?,
        val supplierCode: String?,
        val supplierName: String,
        var expectedNoOfDelivery: Int = 0,
        val stockInLineIds: MutableSet<Long> = mutableSetOf(),
        var itemsInspected: Int = 0,
        var itemsWithIqcIssue: Int = 0,
        var itemsCompletedPutAway: Int = 0
    )

    fun getGoodsReceiptStatus(date: LocalDate): List<GoodsReceiptStatusResponse> {
        val from = date.atStartOfDay()
        val to = date.plusDays(1).atStartOfDay()

        val purchaseOrders = purchaseOrderRepository.findAllByEstimatedArrivalDateRange(from, to)
        val stockInLines = stockInLineRepository.findByReceiptDateAndDeletedFalse(date)
        val todaysPoIds = purchaseOrders.mapNotNull { it.id }.toSet()
        val extraPoIdsCounted = mutableSetOf<Long>()

        val stockInLineIds = stockInLines.mapNotNull { it.id }
        val inspectedLineIdSet = if (stockInLineIds.isEmpty()) {
            emptySet()
        } else {
            qcResultRepository
                .findDistinctStockInLineIdsByStockInLineIdInAndDeletedFalse(stockInLineIds)
                .toSet()
        }
        val failedLineIdSet = if (stockInLineIds.isEmpty()) {
            emptySet()
        } else {
            qcResultRepository
                .findDistinctFailedStockInLineIdsByStockInLineIdInAndDeletedFalse(stockInLineIds)
                .toSet()
        }

        val byPurchaseOrder = mutableMapOf<Long?, Agg>()

        fun upsertAgg(poId: Long?, poCode: String?, supplierId: Long?, supplierCode: String?, supplierName: String): Agg {
            return byPurchaseOrder.getOrPut(poId) {
                Agg(
                    purchaseOrderId = poId,
                    purchaseOrderCode = poCode,
                    supplierId = supplierId,
                    supplierCode = supplierCode,
                    supplierName = supplierName
                )
            }
        }

        // Expected deliveries (by PO estimated arrival date)
        purchaseOrders.forEach { po ->
            val supplier = po.supplier
            val supplierId = supplier?.id
            val supplierCode = supplier?.code
            val supplierName = supplier?.name ?: "N/A"
            val poId = po.id
            val poCode = po.code
            val agg = upsertAgg(poId, poCode, supplierId, supplierCode, supplierName)
            agg.expectedNoOfDelivery = 1
        }

        // Receiving / IQC / Put-away (by stock-in receipt date)
        stockInLines.forEach { sil ->
            val po = sil.purchaseOrder
            val poId = po?.id
            val poCode = po?.code
            val supplier = po?.supplier
            val supplierId = supplier?.id
            val supplierCode = supplier?.code
            val supplierName = supplier?.name ?: "N/A"
            
            val agg = upsertAgg(poId, poCode, supplierId, supplierCode, supplierName)
            
            val silId = sil.id ?: return@forEach

            // If PO not in today's list, mark as expected
            if (poId != null && !todaysPoIds.contains(poId) && extraPoIdsCounted.add(poId)) {
                agg.expectedNoOfDelivery = 1
            } else if (agg.expectedNoOfDelivery == 0) {
                agg.expectedNoOfDelivery = 1
            }

            // Items inspected: any IQC result recorded
            if (inspectedLineIdSet.contains(silId)) {
                agg.itemsInspected += 1
            }

            // Items with IQC issue: any failed IQC criteria
            if (failedLineIdSet.contains(silId)) {
                agg.itemsWithIqcIssue += 1
            }

            // Put-away completed at store: stock-in line completed
            if (sil.status.equals("completed", ignoreCase = true)) {
                agg.itemsCompletedPutAway += 1
            }
        }

        // For each purchase order, fetch ALL its stock-in lines (regardless of receipt date)
        // and check if all have finished IQC
        byPurchaseOrder.values.forEach { agg ->
            if (agg.purchaseOrderId != null) {
                val allStockInLines = stockInLineRepository.findAllByPurchaseOrderIdAndDeletedFalse(agg.purchaseOrderId)
                allStockInLines.ifPresent { lines ->
                    lines.forEach { sil ->
                        val silId = sil.id
                        if (silId != null) {
                            agg.stockInLineIds.add(silId)
                        }
                    }
                }
            }
        }

        // Get all stock-in line IDs from all purchase orders to check IQC status
        val allPoStockInLineIds = byPurchaseOrder.values.flatMap { it.stockInLineIds }.toSet()
        
        // Check IQC status for ALL stock-in lines from all purchase orders
        val allInspectedLineIdSet = if (allPoStockInLineIds.isEmpty()) {
            emptySet()
        } else {
            qcResultRepository
                .findDistinctStockInLineIdsByStockInLineIdInAndDeletedFalse(allPoStockInLineIds.toList())
                .toSet()
        }

        // Build complete PO status map for all POs that appear in the aggregation
        // This includes POs from both purchaseOrders (by estimated arrival date) and stockInLines (by receipt date)
        val allPoIds = byPurchaseOrder.keys.filterNotNull().toSet()
        val allPosWithStatus = allPoIds.mapNotNull { poId ->
            purchaseOrderRepository.findById(poId).orElse(null)?.let { po -> poId to po.status }
        }
        val poStatusMap = allPosWithStatus.toMap()

        return byPurchaseOrder.values
            .map { agg ->
                // Determine whether this PO should be hidden from the dashboard table:
                // hide ONLY when *all items (POLs)* under this PO are fully handled,
                // meaning each PO line has at least one stock-in line and
                // all of its stock-in lines are completed or rejected.
                val shouldHide = agg.purchaseOrderId?.let { poId ->
                    val poLines = purchaseOrderLineRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(poId)
                    if (poLines.isEmpty()) {
                        false
                    } else {
                        poLines.all { pol ->
                            val sils = pol.stockInLines
                            sils.isNotEmpty() && sils.all { sil ->
                                val status = sil.status?.lowercase() ?: ""
                                status == "completed" || status == "rejected"
                            }
                        }
                    }
                } ?: false

                // Order is processed ONLY if:
                // 1. The purchase order status is COMPLETED (all PO lines finished and items put away)
                //    - This is controlled by /po/check/{id} -> PurchaseOrderService.checkPolAndCompletePo
                // 2. Optionally, ALL stock-in lines for the purchase order have finished IQC (regardless of pass/fail)
                //    - We still check IQC to ensure data consistency, but PO status is the primary gate.
                val totalStockInLines = agg.stockInLineIds.size
                val finishedIqcCount = agg.stockInLineIds.count { silId -> silId in allInspectedLineIdSet }
                
                val allItemsFinishedIqc = totalStockInLines > 0 && totalStockInLines == finishedIqcCount

                // PO is considered processed only when business marks it COMPLETED.
                val poCompleted = agg.purchaseOrderId
                    ?.let { poStatusMap[it] == PurchaseOrderStatus.COMPLETED }
                    ?: false

                val receivedCount = if (poCompleted && allItemsFinishedIqc) 1 else 0
                val totalCount = agg.expectedNoOfDelivery
                val statistics = "$receivedCount/$totalCount" + "張單已處理"
                
                GoodsReceiptStatusResponse(
                    supplierId = agg.supplierId,
                    supplierCode = agg.supplierCode,
                    supplierName = agg.supplierName,
                    purchaseOrderCode = agg.purchaseOrderCode,
                    statistics = statistics,
                    expectedNoOfDelivery = agg.expectedNoOfDelivery,
                    noOfOrdersReceivedAtDock = receivedCount,
                    noOfItemsInspected = agg.itemsInspected,
                    noOfItemsWithIqcIssue = agg.itemsWithIqcIssue,
                    noOfItemsCompletedPutAwayAtStore = agg.itemsCompletedPutAway,
                    hideFromDashboard = shouldHide
                )
            }
            .sortedWith(compareBy<GoodsReceiptStatusResponse> { it.supplierName }.thenBy { it.purchaseOrderCode })
    }
}

