package com.ffii.fpsms.modules.stock.service

import com.ffii.core.response.RecordsRes
import com.ffii.fpsms.modules.master.entity.UomConversionRepository
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.StockLedger
import com.ffii.fpsms.modules.stock.web.model.HandleBadItemRequest
import com.ffii.fpsms.modules.stock.web.model.SearchStockIssueRecordRequest
import com.ffii.fpsms.modules.stock.web.model.StockIssueHandleRecordResponse
import com.ffii.fpsms.modules.stock.web.model.StockOutRequest
import com.ffii.fpsms.modules.user.service.UserService
import com.ffii.fpsms.modules.common.SecurityUtils
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.time.LocalDate

@Service
open class StockIssueService(
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val stockOutLineService: StockOutLineService,
    private val stockLedgerRepository: StockLedgerRepository,
    private val uomConversionRepository: UomConversionRepository,
    private val userService: UserService,
    private val stockOutRepository: StockOutRepository,
) {

    @Transactional(rollbackFor = [Exception::class])
    open fun handleBadItem(request: HandleBadItemRequest): MessageResponse {
        try {
            val lotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElse(null)
                ?: return error("NOT_FOUND", "Lot line not found")

            val inQty = lotLine.inQty ?: BigDecimal.ZERO
            val outQty = lotLine.outQty ?: BigDecimal.ZERO
            val holdQty = lotLine.holdQty ?: BigDecimal.ZERO
            val available = inQty.subtract(outQty).subtract(holdQty)
            val qtyBd = BigDecimal.valueOf(request.qty)

            if (qtyBd <= BigDecimal.ZERO) {
                return error("INVALID_QTY", "Quantity must be greater than zero")
            }
            if (qtyBd > available) {
                return error("QTY_EXCEEDS_AVAILABLE", "Quantity exceeds available quantity ($available)")
            }

            val savedStockOutLine = stockOutLineService.createStockOut(
                StockOutRequest(
                    inventoryLotLineId = request.inventoryLotLineId,
                    qty = request.qty,
                    type = "Bad",
                ),
            )

            val stockOut = savedStockOutLine.stockOut
            if (stockOut != null) {
                val remarks = request.remarks?.trim()?.takeIf { it.isNotEmpty() }
                if (remarks != null) {
                    stockOut.remarks = remarks
                }
                val handlerId = request.handler ?: SecurityUtils.getUser().orElse(null)?.id
                if (handlerId != null) {
                    stockOut.handler = handlerId
                }
                stockOutRepository.saveAndFlush(stockOut)
            }

            return MessageResponse(
                id = savedStockOutLine.stockOut?.id,
                name = "Success",
                code = "SUCCESS",
                type = "stock_issue",
                message = "Successfully handled bad item",
                errorPosition = null,
            )
        } catch (e: Exception) {
            return error("ERROR", "Failed to handle bad item: ${e.message}")
        }
    }

    open fun getBadItemHandleRecords(request: SearchStockIssueRecordRequest): RecordsRes<StockIssueHandleRecordResponse> {
        return searchHandleRecords(request, "Bad")
    }

    open fun getExpiryItemHandleRecords(request: SearchStockIssueRecordRequest): RecordsRes<StockIssueHandleRecordResponse> {
        val (startDate, endDateExclusive) = resolveDateRange(request.startDate, request.endDate)
        val itemCode = request.itemCode?.trim()?.takeIf { it.isNotEmpty() }
        val itemName = request.itemName?.trim()?.takeIf { it.isNotEmpty() }
        val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }

        val pageable = PageRequest.of(
            request.pageNum.coerceAtLeast(0),
            request.pageSize.coerceIn(1, 200),
        )

        val page = stockLedgerRepository.findExpiryItemHandleRecords(
            itemCode = itemCode,
            itemName = itemName,
            lotNo = lotNo,
            startDate = startDate,
            endDateExclusive = endDateExclusive,
            pageable = pageable,
        )

        val records = page.content.map { ledger -> toRecordResponse(ledger) }
        return RecordsRes(records, page.totalElements.toInt())
    }

    private fun searchHandleRecords(
        request: SearchStockIssueRecordRequest,
        type: String,
    ): RecordsRes<StockIssueHandleRecordResponse> {
        val (startDate, endDateExclusive) = resolveDateRange(request.startDate, request.endDate)
        val itemCode = request.itemCode?.trim()?.takeIf { it.isNotEmpty() }
        val itemName = request.itemName?.trim()?.takeIf { it.isNotEmpty() }
        val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }

        val pageable = PageRequest.of(
            request.pageNum.coerceAtLeast(0),
            request.pageSize.coerceIn(1, 200),
        )

        val page = stockLedgerRepository.findStockIssueHandleRecords(
            type = type,
            itemCode = itemCode,
            itemName = itemName,
            lotNo = lotNo,
            startDate = startDate,
            endDateExclusive = endDateExclusive,
            pageable = pageable,
        )

        val records = page.content.map { ledger -> toRecordResponse(ledger) }
        return RecordsRes(records, page.totalElements.toInt())
    }

    private fun resolveDateRange(startDate: LocalDate?, endDate: LocalDate?): Pair<LocalDate?, LocalDate?> {
        val start = startDate ?: endDate?.minusMonths(3) ?: LocalDate.now().minusMonths(3)
        val endInclusive = endDate ?: LocalDate.now()
        return Pair(start, endInclusive.plusDays(1))
    }

    private fun toRecordResponse(ledger: StockLedger): StockIssueHandleRecordResponse {
        val stockOutLine = ledger.stockOutLine
        val lotLine = stockOutLine?.inventoryLotLine
        val lot = lotLine?.inventoryLot
        val item = lot?.item ?: ledger.inventory?.item
        val handlerId = stockOutLine?.stockOut?.handler ?: stockOutLine?.handledBy
        val handlerName: String? = handlerId?.let { id ->
            userService.find(id).orElse(null)?.name
        }
        val uomDesc = ledger.uomId?.let { uomId ->
            uomConversionRepository.findByIdAndDeletedFalse(uomId)?.udfudesc
        }
        val qty = BigDecimal(ledger.outQty?.toString() ?: "0")
        val remarks = stockOutLine?.stockOut?.remarks

        return StockIssueHandleRecordResponse(
            id = ledger.id ?: 0L,
            stockOutLineId = stockOutLine?.id,
            stockOutId = stockOutLine?.stockOut?.id,
            handledAt = ledger.date,
            itemId = item?.id ?: ledger.itemId,
            itemCode = ledger.itemCode ?: item?.code,
            itemName = item?.name,
            lotNo = lot?.lotNo,
            storeLocation = lotLine?.warehouse?.code,
            expiryDate = lot?.expiryDate,
            qty = qty,
            uomDesc = uomDesc,
            handlerId = handlerId,
            handlerName = handlerName,
            remarks = remarks,
            type = ledger.type,
        )
    }

    private fun error(code: String, message: String): MessageResponse = MessageResponse(
        id = null,
        name = "Error",
        code = code,
        type = "stock_issue",
        message = message,
        errorPosition = null,
    )
}
