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

import com.ffii.core.response.RecordsRes
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.common.CodeGenerator
import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.service.UomConversionService
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder
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.enums.PurchaseOrderLineStatus
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType
import com.ffii.fpsms.modules.stock.entity.*
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo
import com.ffii.fpsms.modules.stock.web.model.SearchInventoryRequest
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

@Service
open class InventoryService(
    private val jdbcDao: JdbcDao,
    private val inventoryRepository: InventoryRepository,
    private val itemsRepository: ItemsRepository,
    private val uomConversionService: UomConversionService,
    private val uomConversionRepository: UomConversionRepository,
    private val itemUomService: ItemUomService,
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val purchaseOrderLineRepository: PurchaseOrderLineRepository,
    private val currencyRepository: CurrencyRepository,
    private val stockInRepository: StockInRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val warehouseRepository: WarehouseRepository
    ): AbstractBaseEntityService<Inventory, Long, InventoryRepository>(jdbcDao, inventoryRepository)  {
    open fun allInventory(): List<Inventory> {
        // TODO: Replace by actual logic
        val inventory = inventoryRepository.findAll()
        return inventory
    }

    open fun allInventories(): List<InventoryInfo>{
        return inventoryRepository.findInventoryInfoByDeletedIsFalse();
    }

    open fun allInventoriesByPage(request: SearchInventoryRequest): RecordsRes<InventoryInfo>{
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10)
        val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }

        val response = if (lotNo != null) {
            val itemIds = inventoryLotLineRepository.findDistinctItemIdsByLotNo(lotNo)
            if (itemIds.isEmpty()) {
                return RecordsRes(emptyList(), 0)
            }
            inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndItemIdInAndDeletedIsFalse(
                code = request.code,
                name = request.name,
                type = request.type,
                itemIds = itemIds,
                pageable = pageable
            )
        } else {
            inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndDeletedIsFalse(
                code = request.code,
                name = request.name,
                type = request.type,
                pageable = pageable
            )
        }

        val records = response.content
        val total = response.totalElements

        return RecordsRes<InventoryInfo>(records, total.toInt());
    }

    /**
     * FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.0 | 2026-07-17
     * Inventory search page only: no baseUnit/uomId filter;
     * one inventory row per item (latest id). Shared getRecordByPage is unchanged.
     */
    open fun searchInventoriesByLatestInventory(
        request: SearchInventoryRequest,
    ): RecordsRes<InventoryInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10)
        val code = request.code?.trim().orEmpty()
        val name = request.name?.trim().orEmpty()
        val type = request.type?.trim().orEmpty()
        val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }

        val response = if (lotNo != null) {
            val itemIds = inventoryLotLineRepository.findDistinctItemIdsByLotNo(lotNo)
            if (itemIds.isEmpty()) {
                return RecordsRes(emptyList(), 0)
            }
            inventoryRepository.findLatestInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndItemIdInAndDeletedIsFalse(
                code = code,
                name = name,
                type = type,
                itemIds = itemIds,
                pageable = pageable,
            )
        } else {
            inventoryRepository.findLatestInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndDeletedIsFalse(
                code = code,
                name = name,
                type = type,
                pageable = pageable,
            )
        }

        return RecordsRes(response.content, response.totalElements.toInt())
    }

    open fun allInventoriesByItems(items: List<Items>): List<InventoryInfo>{
        return inventoryRepository.findInventoryInfoByItemInAndDeletedIsFalse(items);
    }

    open fun allInventoriesByItemIds(itemIds: List<Long>): List<InventoryInfo>{
        return inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(itemIds);
    }

    open fun assignLotNo(): String {
        val prefix = "LT"
        val midfix = CodeGenerator.DEFAULT_MIDFIX
        val latestCode = inventoryLotRepository.findLatestLotNoByPrefix("${prefix}-${midfix}")
        return CodeGenerator.generateNo(prefix = prefix, latestCode = latestCode)
    }

    fun save_po(): PurchaseOrder {
        val poCode = "MANUAL-IMPORT ${LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}"
        val po = PurchaseOrder().apply {
            this.code = poCode
            this.currency = currencyRepository.findByIdAndDeletedIsFalse(1)
            this.orderDate = LocalDate.now().atStartOfDay()
            this.estimatedArrivalDate = LocalDate.now().atStartOfDay()
            this.completeDate = LocalDate.now().atStartOfDay()
            this.status = PurchaseOrderStatus.COMPLETED
            this.type = PurchaseOrderType.IMPORT
            // set m18Datalog to nullable
        }
        return purchaseOrderRepository.saveAndFlush(po)
    }

    data class InventoryImportExcelField(
        val itemCode: String,
        val expiryDate: LocalDate,
        val qty: BigDecimal,
        val warehouseCode: String,
    )

    fun save_stock_in(po: PurchaseOrder): StockIn {
        val stockIn = StockIn().apply {
            this.code = po.code
            this.orderDate = po.orderDate
            this.estimatedCompleteDate = LocalDate.now()
            this.completeDate = LocalDateTime.now()
            this.purchaseOrder = po
        }
        return stockInRepository.save(stockIn)
    }
    fun save_stock_in_line(po: PurchaseOrder, stockIn: StockIn, pol: MutableList<PurchaseOrderLine>) {
        val entries = pol.map {
            StockInLine()
        }
    }
    fun batch_save(sheet: Sheet, po: PurchaseOrder) {
        var START_ROW_INDEX = 1
        var START_COLUMN_INDEX = 1
        val checkColumnIndex = 1
        val itemCodeColumnIndex = 1
        val expiryDateColumnIndex = 2
        val qtyColumnIndex = 3
        val warehouseColumnIndex = 4
        val columnList = listOf(
            itemCodeColumnIndex,
            expiryDateColumnIndex,
            qtyColumnIndex,
            warehouseColumnIndex,
        )
        var counter = 0
        val BATCH_SIZE = 50
        val entriesList = mutableListOf<PurchaseOrderLine>()
        while (true) {
            val currentRow = sheet.getRow(START_ROW_INDEX)
            val itemCell = currentRow.getCell(itemCodeColumnIndex)
            // do save while ch
            if (entriesList.size == BATCH_SIZE || itemCell == null) {
                if (entriesList.isEmpty()) break
                val polEntries = purchaseOrderLineRepository.saveAllAndFlush(entriesList)
                val stockIn = save_stock_in(po)
                // do save sil
                save_stock_in_line(
                    po = po,
                    stockIn = stockIn,
                    pol = polEntries
                )
                entriesList.clear()
                if (itemCell == null) break
            }
            val expiryDateCell = currentRow.getCell(expiryDateColumnIndex)
            val qtyCell = currentRow.getCell(qtyColumnIndex)
            val warehouseCell = currentRow.getCell(warehouseColumnIndex)
            val request = InventoryImportExcelField(
                itemCode = itemCell.stringCellValue,
                expiryDate = itemCell.dateCellValue
                    .toInstant()
                    .atZone(ZoneId.systemDefault())
                    .toLocalDate(),
                qty = qtyCell.numericCellValue.toBigDecimal(),
                warehouseCode = warehouseCell.stringCellValue
            )
            val item = itemsRepository.findByCodeAndDeletedFalse(request.itemCode)
            val salesUnit = itemUomService.findSalesUnitByItemId(itemId = item!!.id!!)!!.uom
            val purchaseOrderLine = PurchaseOrderLine()
                .apply {
                this.item = item
                this.itemNo = item.code
                this.uom = salesUnit
                this.purchaseOrder = po
                this.price = BigDecimal.ZERO
                this.status = PurchaseOrderLineStatus.COMPLETED
            }
            entriesList.add(purchaseOrderLine)


            START_ROW_INDEX++
            counter++
        }
    }
    open fun importInventoryTestingMethod() {
        val resolver = PathMatchingResourcePatternResolver()
        val TARGET_LOCATION_FILE = "file:C:/Users/2Fi/Desktop/Fourth Wave of BOM Excel/*.xlsx" // CHANGE THIS FOR DIFFERENT FILE
        val excel = resolver.getResource(TARGET_LOCATION_FILE)
        val inputStream = excel.inputStream
        val workbook: Workbook = XSSFWorkbook(inputStream)
        val sheet: Sheet = workbook.getSheetAt(0)

        val po = save_po()
        // saving lines in batch
        batch_save(sheet = sheet, po = po)
    }

    data class ExcelMap(
        val itemCodeColumnIndex: Int,
        val expiryDateColumnIndex: Int,
        val qtyColumnIndex: Int,
        val warehouseColumnIndex: Int,
    )
    open fun importInventoryExcel() {
        val resolver = PathMatchingResourcePatternResolver()
        val TARGET_LOCATION_FILE = "file:C:/Users/2Fi/Desktop/Fourth Wave of BOM Excel/*.xlsx" // CHANGE THIS FOR DIFFERENT FILE
        val excel = resolver.getResource(TARGET_LOCATION_FILE)
        val inputStream = excel.inputStream
        val workbook: Workbook = XSSFWorkbook(inputStream)
        val sheet: Sheet = workbook.getSheetAt(0)

        var START_ROW_INDEX = 1
        var START_COLUMN_INDEX = 1
        val columnMap = ExcelMap(
            1,
            2,
            3,
            4
        )
        val checkingRow = sheet.getRow(START_ROW_INDEX)
        val checkingFirstCell = checkingRow.getCell(columnMap.itemCodeColumnIndex)
        val purchaseOrderLineList = mutableListOf<PurchaseOrderLine>()
        val requestList = mutableListOf<InventoryImportExcelField>()
        // purchase order
        val po = save_po()
        // purchase order mostly
        try {
            while (checkingFirstCell != null) {
                val currentRow = sheet.getRow(START_ROW_INDEX)
                val itemCell = currentRow.getCell(columnMap.itemCodeColumnIndex)
                val expiryDateCell = currentRow.getCell(columnMap.expiryDateColumnIndex)
                val qtyCell = currentRow.getCell(columnMap.qtyColumnIndex)
                val warehouseCell = currentRow.getCell(columnMap.warehouseColumnIndex)
                // info
                val request = InventoryImportExcelField(
                    itemCode = itemCell.stringCellValue,
                    expiryDate = itemCell.dateCellValue
                        .toInstant()
                        .atZone(ZoneId.systemDefault())
                        .toLocalDate(),
                    qty = qtyCell.numericCellValue.toBigDecimal(),
                    warehouseCode = warehouseCell.stringCellValue
                )
                requestList.add(request)
                val item = itemsRepository.findByCodeAndDeletedFalse(request.itemCode)

                val salesUnit = itemUomService.findSalesUnitByItemId(itemId = item!!.id!!)!!.uom
                val stockUnit = itemUomService.findStockUnitByItemId(itemId = item.id!!)!!.uom
                // purchase order line
                val purchaseOrderLine = PurchaseOrderLine()
                    .apply {
                        this.item = item
                        this.itemNo = item.code
                        this.uom = stockUnit
                        this.purchaseOrder = po
                        this.price = BigDecimal.ZERO
                        this.status = PurchaseOrderLineStatus.COMPLETED
                    }
                purchaseOrderLineList.add(purchaseOrderLine)
                START_ROW_INDEX++
            }
        } catch (e:Error) {
            // write log to pol excel
        }

        val polList = purchaseOrderLineRepository.saveAllAndFlush(purchaseOrderLineList)
        // stock in
        val stockIn = save_stock_in(po)
        // stock in line
        val oneYearExpiry = 1L
        val stockInLineEntries = polList.map { pol ->
//        val newLotNo = CodeGenerator.generateCode(
//            prefix = "MPO",
//            itemId = pol.item!!.id!!,
//            count = pol.id!!.toInt()
//        )
            val newLotNo = assignLotNo()
            StockInLine().apply {
                this.purchaseOrder = po
                this.purchaseOrderLine = pol
                this.item = pol.item
                this.itemNo = pol.item!!.code
                this.stockIn = stockIn
                this.acceptedQty = pol.qty
                this.receiptDate = LocalDateTime.now()
                this.expiryDate = LocalDate.now().plusYears(oneYearExpiry) // default
                this.lotNo = newLotNo
            }
        }
        val savedStockInLineEntries = stockInLineRepository.saveAllAndFlush(stockInLineEntries)
        // inventory lot
        val inventoryLotEntries = savedStockInLineEntries.map { stockInLine ->
            InventoryLot().apply {
                this.item = stockInLine.item
                this.stockInLine = stockInLine
                this.stockInDate = LocalDateTime.now()
                this.expiryDate = stockInLine.expiryDate
                this.lotNo = stockInLine.lotNo
            }
        }
        val savedInventoryLotEntries = inventoryLotRepository.saveAllAndFlush(inventoryLotEntries)
        // inventory lot line
//        val inventoryLotLIneEntries = inventoryLotEntries.map {inventoryLot ->
        val inventoryLotLineEntries = requestList.mapIndexed { index, request ->
            val inventoryLot = savedInventoryLotEntries[index]
            val warehouse = warehouseRepository.findAll().find { it.code == request.warehouseCode }!!
//            val salesUnit = itemUomService.findSalesUnitByItemId(itemId = inventoryLot.item!!.id!!)
            val stockUnit = itemUomService.findStockUnitByItemId(itemId = inventoryLot.item!!.id!!)
//            val zero = BigDecimal.ZERO
//            val one = BigDecimal.ONE
//            val ratio = (salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one)

            InventoryLotLine().apply {
                this.inventoryLot = inventoryLot
                this.warehouse = warehouse
                this.inQty = request.qty
                this.status = InventoryLotLineStatus.AVAILABLE
                this.stockUom = stockUnit
//                this.stockUom = salesUnit
            }
        }
        val savedInventoryLotLine = inventoryLotLineRepository.saveAllAndFlush(inventoryLotLineEntries)
        val updateStockInLineEntries = savedInventoryLotLine.mapIndexed { index, inventoryLotLine ->
            savedStockInLineEntries[index].apply {
                this.inventoryLotLine = inventoryLotLine
                this.inventoryLot = inventoryLotLine.inventoryLot
            }
        }
        val savedNewStockInLineEntries = stockInLineRepository.saveAllAndFlush(updateStockInLineEntries)

    }


}