package com.ffii.fpsms.modules.master.service

import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.core.utils.ExcelUtils
import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.Warehouse
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo
import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest
import com.ffii.fpsms.modules.master.web.models.NewWarehouseRequest
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.StockInLine
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.StockInRepository
import com.ffii.fpsms.modules.stock.service.InventoryLotService
import com.ffii.fpsms.modules.stock.service.StockInService
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.math.BigDecimal
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.master.web.models.ExcelWarehouseData
import com.ffii.core.exception.BadRequestException
import com.ffii.fpsms.modules.master.web.models.MissingStockTakeSectionIssueItem
import com.ffii.fpsms.modules.master.web.models.MissingStockTakeSectionIssuesResponse
import com.ffii.fpsms.modules.master.web.models.StockTakeSectionInfo
import com.ffii.fpsms.modules.master.web.models.UpdateSectionDescriptionRequest
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
@Service
open class WarehouseService(
    private val jdbcDao: JdbcDao,
    private val warehouseRepository: WarehouseRepository,
) : AbstractBaseEntityService<Warehouse, Long, WarehouseRepository>(jdbcDao, warehouseRepository) {
    open fun getWarehouses(): List<Warehouse> {
        return warehouseRepository.findAll().filter { it.deleted == false }
    }

    open fun findCombo(): List<WarehouseCombo> {
        return warehouseRepository.findWarehouseComboByDeletedFalse();
    }

    open fun findById(id: Long): Warehouse? {
        return warehouseRepository.findByIdAndDeletedIsFalse(id);
    }

    open fun findByCode(code: String): Warehouse? {
        return warehouseRepository.findByCodeAndDeletedIsFalse(code);
    }

    open fun markDeleted(id: Long): List<Warehouse> {
        val warehouse = warehouseRepository.findById(id).orElseThrow().apply {
            deleted = true
        }

        warehouseRepository.save(warehouse)

        return getWarehouses()
    }

    open fun saveWarehouse(request: SaveWarehouseRequest): Warehouse {
        val warehouse = request.id?.let { warehouseRepository.findById(it).getOrNull() } ?: Warehouse();
        val section = request.stockTakeSection?.takeIf { it.isNotBlank() }
        if (section != null) {
            val existingList = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(section)
                .filter { it.id != request.id }  // exclude current record when edit
            if (existingList.isNotEmpty()) {
                val existingDesc = existingList.mapNotNull { it.stockTakeSectionDescription }.firstOrNull()?.trim() ?: ""
                val inputDesc = request.stockTakeSectionDescription?.trim() ?: ""
                if (existingDesc != inputDesc) {
                    throw BadRequestException("stockTakeSectionDescription not same")
                }
            }
        }
        warehouse.apply {
            // Generate code, name, description if not provided but store_id, warehouse, area, slot are provided
            if (request.code == null && request.store_id != null && request.warehouse != null && request.area != null && request.slot != null) {
                code = "${request.store_id}-${request.warehouse}-${request.area}-${request.slot}"
            } else if (request.code != null) {
                code = request.code
            }
            
            if (request.name == null && request.store_id != null && request.warehouse != null) {
                name = "${request.store_id}-${request.warehouse}"
            } else if (request.name != null) {
                name = request.name
            }
            
            if (request.description == null && request.store_id != null && request.warehouse != null) {
                description = "${request.store_id}-${request.warehouse}"
            } else if (request.description != null) {
                description = request.description
            }
            
            if (request.capacity == null) {
                capacity = BigDecimal(10000)
            } else {
                capacity = request.capacity
            }

            if (request.order == null && request.id == null) {
                // Set a default order for new warehouses
                val maxOrder = warehouseRepository.findAll()
                    .mapNotNull { it.order?.toIntOrNull() }
                    .maxOrNull() ?: 0
                order = (maxOrder + 1).toString()
            } else if (request.order != null) {
                order = request.order
            }
            
            if (request.store_id != null) store_id = request.store_id
            if (request.warehouse != null) this.warehouse = request.warehouse
            if (request.area != null) area = request.area
            if (request.slot != null) slot = request.slot
            if (request.stockTakeSection != null) stockTakeSection = request.stockTakeSection
            if (request.stockTakeSectionDescription != null) stockTakeSectionDescription = request.stockTakeSectionDescription
        };

        return warehouseRepository.save(warehouse);
    }

    open fun importExcel(workbook: Workbook?): String {
        logger.info("--------- Start - Import Warehouse Excel -------");

        if (workbook == null) {
            logger.error("No Excel Import");
            return "Import Excel failure";
        }
        val sheet: Sheet = workbook.getSheetAt(0);

        // Columns
        val COLUMN_WAREHOSE_INDEX = 1;
        val COLUMN_ZONE_INDEX = 2;
        val COLUMN_SLOT_INDEX = 3;
        val COLUMN_FLOOR_INDEX = 4;
        val COLUMN_PLACE_INDEX = 5;

        val START_ROW_INDEX = 1;

        // Start Import
        for (i in START_ROW_INDEX..<sheet.lastRowNum) {
            val row = sheet.getRow(i)

            try {
                val floor = ExcelUtils.getStringValue(row.getCell(COLUMN_FLOOR_INDEX)).trim()
                val place = ExcelUtils.getStringValue(row.getCell(COLUMN_PLACE_INDEX)).trim()
                val code = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOSE_INDEX)).trim()
                val zone = ExcelUtils.getStringValue(row.getCell(COLUMN_ZONE_INDEX)).trim()
                val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX)).trim()
//                logger.info("Warehouse code - zone - slot: ${row.getCell(COLUMN_WAREHOSE_INDEX).cellType} - ${row.getCell(COLUMN_ZONE_INDEX).cellType} - ${row.getCell(COLUMN_SLOT_INDEX).cellType}")

                val defaultCapacity = BigDecimal(10000)
                val warehouseCode = "$code-$zone-$slot"
                val warehouseName = "$floor-$place"
                val existingWarehouse = findByCode(warehouseCode)
                //check id
                if (existingWarehouse == null) {
                    val warehouseRequest = SaveWarehouseRequest(
                        code = warehouseCode,
                        name = warehouseName,
                        description = warehouseName,
                        capacity = defaultCapacity
                    )
                    saveWarehouse(warehouseRequest)
                }
            } catch (e: Exception) {
                logger.error("Import Error (Warehouse Error): ${e.message}")
            }
        }
        logger.info("--------- End - Import Warehouse Excel -------")
        return "Import Excel success";
    }
    open fun importNewExcel(workbook: Workbook?): String {
        logger.info("--------- Start - Import Warehouse Excel -------");
    
        if (workbook == null) {
            logger.error("No Excel Import");
            return "Import Excel failure";
        }
        val sheet: Sheet = workbook.getSheetAt(0);
    
        // Columns
        val COLUMN_REF_INDEX = 0;
        val COLUMN_WAREHOSE_INDEX = 1;
        val COLUMN_ZONE_INDEX = 2;
        val COLUMN_SLOT_INDEX = 3;
        val COLUMN_FLOOR_INDEX = 4;
        val COLUMN_PICK_ROUTING_INDEX = 5;
        val COLUMN_STORE_LOCATION_INDEX = 6;
        val COLUMN_STOCK_TAKE_TABLE_INDEX = 7;
        val COLUMN_COMPANY_INDEX = 8;
        
        val START_ROW_INDEX = 5;
    
        // first step: collect all Excel data (contains row index, for debugging)
        data class ExcelWarehouseDataWithRow(
            val rowIndex: Int,
            val warehouse: String,
            val area: String,
            val slot: String,
            val ref: String?
        )
    
        val excelDataList = mutableListOf<ExcelWarehouseDataWithRow>()
        var skippedRows = 0
    
        for (i in START_ROW_INDEX..<sheet.lastRowNum) {
            val row = sheet.getRow(i)
            if (row == null) {
                skippedRows++
                continue
            }
    
            try {
                val ref = ExcelUtils.getStringValue(row.getCell(COLUMN_REF_INDEX))?.trim()
                val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOSE_INDEX))?.trim()
                val area = ExcelUtils.getStringValue(row.getCell(COLUMN_ZONE_INDEX))?.trim()
                val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim()
    
                if (warehouse.isNullOrBlank() || area.isNullOrBlank() || slot.isNullOrBlank()) {
                    skippedRows++
                    continue
                }
    
                excelDataList.add(ExcelWarehouseDataWithRow(i, warehouse, area, slot,ref))
            } catch (e: Exception) {
                logger.error("Read Error (Row ${i + 1}): ${e.message}")
                skippedRows++
            }
        }
    
        logger.info("Total rows in Excel: ${sheet.lastRowNum - START_ROW_INDEX + 1}, Valid rows collected: ${excelDataList.size}, Skipped: $skippedRows")
    
        // second step: calculate order map (using ExcelWarehouseData)
        val excelDataForOrder = excelDataList.map {
            ExcelWarehouseData(it.warehouse, it.area, it.slot, it.ref ?: "${it.rowIndex}")  // 如果 ref 为空，使用 rowIndex
        }
        val orderMap = calculateOrderMap(excelDataForOrder)
    
        logger.info("Order map size: ${orderMap.size}")
    
        // third step: use orderMap to match in the loop
        var updateCount = 0
        var createCount = 0
        var skippedInSecondLoop = 0
       // val processedKeys = mutableSetOf<String>()
    
        for (i in START_ROW_INDEX..<sheet.lastRowNum) {
            val row = sheet.getRow(i)
            if (row == null) continue
    
            try {
                val ref = ExcelUtils.getStringValue(row.getCell(COLUMN_REF_INDEX))?.trim()
                val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOSE_INDEX))?.trim()
                val area = ExcelUtils.getStringValue(row.getCell(COLUMN_ZONE_INDEX))?.trim()
                val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim()
                val store_id = ExcelUtils.getStringValue(row.getCell(COLUMN_FLOOR_INDEX))?.trim()
                val storeLocation = ExcelUtils.getStringValue(row.getCell(COLUMN_STORE_LOCATION_INDEX))?.trim()
                val stockTakeTable = ExcelUtils.getStringValue(row.getCell(COLUMN_STOCK_TAKE_TABLE_INDEX))?.trim()
                val company = ExcelUtils.getStringValue(row.getCell(COLUMN_COMPANY_INDEX))?.trim()
    
                if (warehouse.isNullOrBlank() || area.isNullOrBlank() || slot.isNullOrBlank()) {
                    skippedInSecondLoop++
                    continue
                }

                // use unique identifier from orderMap to get order
                val refKey = ref ?: "${i + 1}"
                val orderInt = orderMap[refKey]  // This is Int

                if (orderInt == null) {
                    logger.warn("Order not found for key: ${refKey}Key at row ${i + 1}")
                    skippedInSecondLoop++
                    continue
                }

                val order = orderInt.toString()  // Convert Int to String

                if (order == null) {
                    logger.warn("Order not found for key: ${refKey}Key at row ${i + 1}")
                    skippedInSecondLoop++
                    continue
                }

                val capacity = BigDecimal(10000)
                val code = "$store_id-$warehouse-$area-$slot"
                val name = "$store_id-$storeLocation"
                val description = "$store_id-$storeLocation"


                // check if the warehouse exists
                val existingWarehouse = if (!ref.isNullOrBlank()) {
                    try {
                        val refId = ref.toLong()
                        warehouseRepository.findById(refId).orElse(null)?.takeIf { !it.deleted }
                    } catch (e: NumberFormatException) {
                        logger.warn("Invalid ref format: $ref at row ${i + 1}, treating as new record")
                        null
                    }
                } else {
                    null
                }

                if (existingWarehouse != null) {
                    // update the warehouse
                    existingWarehouse.apply {
                        this.code = code
                        this.name = name
                        this.description = description
                        this.warehouse = warehouse
                        this.area = area
                        this.store_id = store_id
                        this.slot = slot
                        this.order = order
                    }
                    warehouseRepository.save(existingWarehouse)
                    updateCount++
                } else {
                    // create the warehouse
                    val newWarehouse = Warehouse().apply {
                        this.code = code
                        this.name = name
                        this.description = description
                        this.capacity = capacity
                        this.warehouse = warehouse
                        this.area = area
                        this.store_id = store_id
                        this.slot = slot
                        this.order = order
                    }
                    warehouseRepository.save(newWarehouse)
                    createCount++
                }
            } catch (e: Exception) {
                logger.error("Import Error (Row ${i + 1}): ${e.message}")
                skippedInSecondLoop++
            }
        }
    
        logger.info("--------- End - Import Warehouse Excel - Created: $createCount, Updated: $updateCount, Skipped in second loop: $skippedInSecondLoop -------")
        return "Import Excel success - Created: $createCount, Updated: $updateCount, Skipped: $skippedInSecondLoop";
    }

    // calculate order map: receive Excel data list, return Map<unique identifier, order>
    private fun calculateOrderMap(excelDataList: List<ExcelWarehouseData>): Map<String, Int> {
        data class WarehouseWithSort(
            val ref: String,  // ref as unique identifier
            val warehouse: String,
            val area: String,
            val slot: String,
            val sortValue: Long
        )
    
    
        val sortedWarehouses = excelDataList.map { data ->
            val slot = data.slot.toIntOrNull() ?: 0
            val sortValue = calculateSortValue(data.warehouse, data.area, slot)
            // use ref as unique identifier (ref is stored in rowIndex field)
            val ref = data.rowIndex
            WarehouseWithSort(ref, data.warehouse, data.area, data.slot, sortValue)
        }.sortedBy { it.sortValue }  // sort by sortValue
    
        // assign consecutive order (1, 2, 3...), return Map<ref, order>
        return sortedWarehouses.mapIndexed { index, warehouseWithSort ->
            warehouseWithSort.ref to (index + 1)  // ref -> order
        }.toMap()
    }
    
    
    // calculate sort value for a single warehouse (consistent with MySQL logic)
    private fun calculateSortValue(warehouseStr: String, areaStr: String, slot: Int): Long {
        // extract floor number
        val floorNumber = if (warehouseStr.length >= 2) {
            warehouseStr.substring(1, 2).toIntOrNull() ?: 99
        } else 99
    
        // extract letter part
        val letterPart = if (areaStr.startsWith("#") && areaStr.length >= 2) {
            areaStr.substring(1, 2)
        } else null
    
        // calculate floor sort value
        val floorOrder = when (floorNumber) {
            2 -> 1
            3 -> 2
            4 -> 3
            else -> 99
        }
    
        // calculate letter sort value (consistent with MySQL logic)
        val letterOrder = when {
            letterPart == null -> 9999
            letterPart in listOf("C", "B", "A", "E", "F", "G", "H", "I", "N", "M", "L", "P", "Q", "R", "S", "Y", "O", "K", "D") -> {
                val orderMap = mapOf(
                    "C" to 1, "B" to 2, "A" to 3, "E" to 4, "F" to 5, "G" to 6, "H" to 7, "I" to 8,
                    "N" to 9, "M" to 10, "L" to 11, "P" to 12, "Q" to 13, "R" to 14, "S" to 15,
                    "Y" to 16, "O" to 17, "K" to 18, "D" to 19
                )
                orderMap[letterPart] ?: 9999
            }
            else -> 1000 + letterPart[0].code
        }
    
        // calculate total sort value: floor * 10000 + letter * 100 + slot
        return floorOrder * 10000L + letterOrder * 100L + slot
    }

    open fun getMissingStockTakeSectionIssues(limit: Int): MissingStockTakeSectionIssuesResponse {
        val capped = limit.coerceIn(1, 200)
        val count = warehouseRepository.countMissingStockTakeSection()
        val sort = Sort.by(Sort.Order.asc("store_id"), Sort.Order.asc("code"))
        val page = warehouseRepository.findMissingStockTakeSection(PageRequest.of(0, capped, sort))
        val items = page.content.map { w ->
            MissingStockTakeSectionIssueItem(
                id = w.id!!,
                code = w.code,
                storeId = w.store_id,
                warehouse = w.warehouse,
                area = w.area,
                slot = w.slot,
                order = w.order,
            )
        }
        return MissingStockTakeSectionIssuesResponse(
            count = count,
            limit = capped,
            items = items,
        )
    }

    open fun getStockTakeSections(): List<StockTakeSectionInfo> {
        val all = warehouseRepository.findAllByDeletedIsFalse()
            .filter { it.stockTakeSection != null && it.stockTakeSection!!.isNotBlank() }
        val grouped = all.groupBy { it.stockTakeSection!! }
        return grouped.map { (section, list) ->
            val desc = list.mapNotNull { it.stockTakeSectionDescription }.firstOrNull()?.trim()
            val storeId = list.asSequence()
                .mapNotNull { it.store_id?.trim() }
                .firstOrNull { it.isNotBlank() }
            val warehouseArea = list.asSequence()
                .mapNotNull { it.area?.trim() }
                .firstOrNull { it.isNotBlank() }
            StockTakeSectionInfo(
                stockTakeSection = section,
                stockTakeSectionDescription = desc,
                warehouseCount = list.size.toLong(),
                storeId = storeId,
                warehouseArea = warehouseArea,
            )
        }.sortedBy { it.stockTakeSection }
    }
    
    open fun updateSectionDescription(section: String, newDescription: String?) {
        val list = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(section)
        list.forEach { w ->
            w.stockTakeSectionDescription = newDescription?.takeIf { it.isNotBlank() }
            warehouseRepository.save(w)
        }
    }
    
    open fun clearWarehouseSection(warehouseId: Long) {
        val w = warehouseRepository.findById(warehouseId).orElseThrow()
        w.stockTakeSection = null
        w.stockTakeSectionDescription = null
        warehouseRepository.save(w)
    }
}