package com.ffii.fpsms.modules.deliveryOrder.service

import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderLine
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderLineStatus
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderLineRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderLineResponse
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.service.UomConversionService
import org.springframework.stereotype.Service
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull

@Service
open class DeliveryOrderLineService(
    private val deliveryOrderService: DeliveryOrderService,
    private val deliveryOrderRepository: DeliveryOrderRepository,
    private val deliveryOrderLineRepository: DeliveryOrderLineRepository,
    private val itemsRepository: ItemsRepository,
    private val m18DataLogRepository: M18DataLogRepository,
    private val uomConversionService: UomConversionService,
) {
    open fun findDeliveryOrderLineByM18Id(m18DataLogId: Long): DeliveryOrderLine? {
        return deliveryOrderLineRepository.findByM18DataLogIdAndDeletedIsFalse(m18DataLogId)
    }

    open fun saveDeliveryOrderLine(request: SaveDeliveryOrderLineRequest): SaveDeliveryOrderLineResponse {
        val deliveryOrderLine = request.id?.let {
            deliveryOrderLineRepository.findById(it).getOrDefault(
                DeliveryOrderLine()
            )
        } ?: DeliveryOrderLine();
        val item = request.itemId?.let { itemsRepository.findById(it).getOrNull() }
        val deliveryOrder = request.deliveryOrderId?.let { deliveryOrderRepository.findById(it).getOrNull() }
        val status = request.status?.let { status -> DeliveryOrderLineStatus.entries.find { it.value == status } }
        val m18DataLog = request.m18DataLogId?.let { m18DataLogRepository.findById(it).getOrNull() }
        val uom = request.uomId?.let { uomConversionService.find(it).getOrNull() }
        val uomM18 = request.uomIdM18?.let { uomConversionService.find(it).getOrNull() }

        deliveryOrderLine.apply {
            this.item = item
            itemNo = item?.code
            this.deliveryOrder = deliveryOrder
            qty = request.qty
            qtyM18 = request.qtyM18
            up = request.up
            price = request.price
            this.uom = uom
            this.uomM18 = uomM18
            this.status = status
            this.m18DataLog = m18DataLog ?: this.m18DataLog
            m18Discount = request.m18Discount
            m18Lot = request.m18Lot
        }

        val savedDeliveryOrderLine = deliveryOrderLineRepository.saveAndFlush(deliveryOrderLine).let {
            SaveDeliveryOrderLineResponse(
                id = it.id,
                itemNo = it.itemNo,
                qty = it.qty,
                qtyM18 = it.qtyM18,
                price = it.price,
                status = it.status?.value
            )
        }

        return savedDeliveryOrderLine
    }

    open fun markDeletedLinesWithFeeItems(): Int {
        val feeLines = deliveryOrderLineRepository.findAllByDeletedIsFalseAndItemIsFeeTrue()
        feeLines.forEach { line ->
            line.deleted = true
            deliveryOrderLineRepository.saveAndFlush(line)
        }
        return feeLines.size
    }

    open fun markDeletedLinesMissingFromM18(
        deliveryOrderId: Long,
        existingM18LineIds: Set<Long>,
        m18RefType: String
    ): Int {
        val localLines = deliveryOrderLineRepository.findAllByDeliveryOrderIdAndM18RefTypeAndDeletedIsFalse(
            deliveryOrderId = deliveryOrderId,
            refType = m18RefType
        )

        val linesToDelete = localLines.filter { line ->
            val m18LineId = line.m18DataLog?.m18Id
            m18LineId == null || !existingM18LineIds.contains(m18LineId)
        }

        linesToDelete.forEach { it.deleted = true }
        if (linesToDelete.isNotEmpty()) {
            deliveryOrderLineRepository.saveAll(linesToDelete)
            deliveryOrderLineRepository.flush()
        }
        return linesToDelete.size
    }

    /**
     * After M18 sync, multiple [DeliveryOrderLine] rows can exist for the same M18 line id: each sync
     * inserts a new m18_data_log row, and lookup by latest log id may not attach to the row still
     * pointing at an older log. Keep the newest row (highest id) per m18DataLog.m18Id and mark others deleted.
     */
    open fun markDuplicateDeliveryOrderLinesForSameM18LineId(
        deliveryOrderId: Long,
        existingM18LineIds: Set<Long>,
        m18RefType: String
    ): Int {
        val localLines = deliveryOrderLineRepository.findAllByDeliveryOrderIdAndM18RefTypeAndDeletedIsFalse(
            deliveryOrderId = deliveryOrderId,
            refType = m18RefType
        )
        val byM18LineId = localLines
            .mapNotNull { line ->
                val mid = line.m18DataLog?.m18Id ?: return@mapNotNull null
                if (mid !in existingM18LineIds) return@mapNotNull null
                mid to line
            }
            .groupBy({ it.first }, { it.second })

        val toSoftDelete = mutableListOf<DeliveryOrderLine>()
        for ((_, lines) in byM18LineId) {
            if (lines.size <= 1) continue
            val sorted = lines.sortedByDescending { it.id ?: 0L }
            toSoftDelete.addAll(sorted.drop(1))
        }
        toSoftDelete.forEach { it.deleted = true }
        if (toSoftDelete.isNotEmpty()) {
            deliveryOrderLineRepository.saveAll(toSoftDelete)
            deliveryOrderLineRepository.flush()
        }
        return toSoftDelete.size
    }
}