package com.ffii.fpsms.modules.purchaseOrder.service

import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.service.CurrencyService
import com.ffii.fpsms.modules.master.service.ItemsService
import com.ffii.fpsms.modules.master.service.UomConversionService
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.projections.PurchaseOrderLineInfo
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineRequest
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineResponse
import org.springframework.stereotype.Service
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull

@Service
open class PurchaseOrderLineService(
    private val purchaseOrderLineRepository: PurchaseOrderLineRepository,
    private val itemsService: ItemsService,
    private val itemsRepository: ItemsRepository,
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val m18DataLogRepository: M18DataLogRepository,
    private val currencyService: CurrencyService,
    private val uomConversionService: UomConversionService,
) {
    open fun allPurchaseOrderLine(): List<PurchaseOrderLine> {
        return purchaseOrderLineRepository.findAll()
    }

    open fun findPurchaseOrderLineByM18Id(m18DataLogId: Long): PurchaseOrderLine? {
        return purchaseOrderLineRepository.findByM18DataLogIdAndDeletedIsFalse(m18DataLogId)
    }

    /**
     * Resolve local PO line for M18 sync by [purchaseOrderId] + M18 line id ([m18LineId] = pot line id).
     * Prefer this over [findPurchaseOrderLineByM18Id] so repeated syncs update the same row instead of inserting duplicates.
     */
    open fun findPurchaseOrderLineByPurchaseOrderAndM18LineId(
        purchaseOrderId: Long,
        m18LineId: Long
    ): PurchaseOrderLine? =
        purchaseOrderLineRepository
            .findAllByPurchaseOrderIdAndM18LineIdOrderByIdDesc(purchaseOrderId, m18LineId)
            .firstOrNull()

    /**
     * Mark as deleted any local PO lines for this PO that were synced from M18 but whose M18 line id
     * is not in the given set (i.e. the line was deleted in M18).
     * @return Pair of (number of lines marked as deleted, set of itemIds of those lines)
     */
    open fun markDeletedLinesNotInM18(purchaseOrderId: Long, m18LineIds: Set<Long>): Pair<Int, Set<Long>> {
        val linesFromM18 =
            purchaseOrderLineRepository.findAllByPurchaseOrderIdAndDeletedIsFalseAndM18DataLogIsNotNull(purchaseOrderId)
        val affectedItemIds = mutableSetOf<Long>()
        var count = 0
        linesFromM18.forEach { line ->
            val m18Id = line.m18DataLog?.m18Id ?: return@forEach
            if (m18Id !in m18LineIds) {
                line.item?.id?.let { affectedItemIds.add(it) }
                line.deleted = true
                purchaseOrderLineRepository.saveAndFlush(line)
                count++
            }
        }
        return Pair(count, affectedItemIds)
    }

    /**
     * Same M18 line id can appear on multiple local PO lines after repeated sync (new m18_data_log each run).
     * Keep the newest line (highest id) per m18DataLog.m18Id and mark the rest deleted.
     * @return Pair of (number deleted, itemIds touched for pricing refresh)
     */
    open fun markDuplicatePurchaseOrderLinesForSameM18LineId(
        purchaseOrderId: Long,
        existingM18LineIds: Set<Long>
    ): Pair<Int, Set<Long>> {
        val linesFromM18 =
            purchaseOrderLineRepository.findAllByPurchaseOrderIdAndDeletedIsFalseAndM18DataLogIsNotNull(purchaseOrderId)
        val byM18LineId = linesFromM18
            .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 affectedItemIds = mutableSetOf<Long>()
        val toSoftDelete = mutableListOf<PurchaseOrderLine>()
        for ((_, lines) in byM18LineId) {
            if (lines.size <= 1) continue
            val sorted = lines.sortedByDescending { it.id ?: 0L }
            val dupes = sorted.drop(1)
            dupes.forEach { line ->
                line.item?.id?.let { affectedItemIds.add(it) }
                line.deleted = true
            }
            toSoftDelete.addAll(dupes)
        }
        if (toSoftDelete.isNotEmpty()) {
            purchaseOrderLineRepository.saveAll(toSoftDelete)
            purchaseOrderLineRepository.flush()
        }
        return Pair(toSoftDelete.size, affectedItemIds)
    }

    open fun findAllPoLineInfoByPoId(poId: Long): List<PurchaseOrderLineInfo> {
        return purchaseOrderLineRepository.findAllPurchaseOrderLineInfoByPurchaseOrderIdAndDeletedIsFalse(poId)
    }

    open fun savePurchaseOrderLine(request: SavePurchaseOrderLineRequest): SavePurchaseOrderLineResponse {
        val purchaseOrderLine =
            request.id?.let { purchaseOrderLineRepository.findById(it).getOrDefault(PurchaseOrderLine()) }
                ?: PurchaseOrderLine()
        val item = request.itemId?.let { itemsRepository.findById(it).getOrNull() }
        val purchaseOrder = request.purchaseOrderId?.let { purchaseOrderRepository.findById(it).getOrNull() }
        val status = request.status?.let { status -> PurchaseOrderLineStatus.entries.find { it.value == status } }
        val m18DataLog = request.m18DataLogId?.let { m18DataLogRepository.findById(it).getOrNull() }
//        val currency = request.m18CurrencyId?.let { currencyService.findByM18Id(it) }
//            ?: request.currencyId?.let { currencyService.findById(it) }
        val uom = request.uomId?.let { uomConversionService.find(it).getOrNull() }
        val uomM18 = request.uomIdM18?.let { uomConversionService.find(it).getOrNull() }

        purchaseOrderLine.apply {
            this.item = item
            itemNo = item?.code
            this.purchaseOrder = purchaseOrder
            qty = request.qty
            qtyM18 = request.qtyM18
            up = request.up
            price = request.price
            this.uom = uom
            this.uomM18 = uomM18
//            this.currency = currency
            this.status = status
            this.m18DataLog = m18DataLog ?: this.m18DataLog
            m18Discount = request.m18Discount
            m18Lot = request.m18Lot
        }

        val savedPurchaseOrderLine = purchaseOrderLineRepository.saveAndFlush(purchaseOrderLine).let {
            SavePurchaseOrderLineResponse(
                id = it.id,
                itemNo = it.itemNo,
                qty = it.qty,
                price = it.price,
//                currency = currency?.name,
                status = it.status?.value
            )
        }

        return savedPurchaseOrderLine
    }

    /**
     * @return Pair of (number of lines marked as deleted, set of itemIds of those lines)
     */
    open fun markDeletedLinesWithFeeItems(): Pair<Int, Set<Long>> {
        val feeLines = purchaseOrderLineRepository.findAllByDeletedIsFalseAndItemIsFeeTrue()
        val affectedItemIds = feeLines.mapNotNull { it.item?.id }.toSet()
        feeLines.forEach { line ->
            line.deleted = true
            purchaseOrderLineRepository.saveAndFlush(line)
        }
        return Pair(feeLines.size, affectedItemIds)
    }
}