package com.ffii.fpsms.modules.pickOrder.service

import com.ffii.fpsms.modules.pickOrder.entity.Truck
import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import com.ffii.fpsms.modules.pickOrder.web.models.TruckLaneScheduleExcelPreviewRow
import com.ffii.fpsms.modules.pickOrder.web.models.TruckLaneScheduleExcelRowError
import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.DataFormatter
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.stereotype.Component
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.Locale

@Component
class TruckLaneScheduleExcelSupport(
    private val truckRepository: TruckRepository,
    private val truckService: TruckService,
) {
    private val formatter = DataFormatter()

    private val headerAliases: Map<String, Set<String>> = mapOf(
        "shop" to setOf("shopid", "shopcode", "shop_id", "shop_code", "店鋪", "店舖編號"),
        "targetlane" to setOf("targetlane", "target_lane", "車線", "目標車線"),
        "targetremark" to setOf("targetremark", "target_remark", "備註"),
        "targetstoreid" to setOf("targetstoreid", "target_store_id", "storeid", "store_id", "樓層"),
        "executedate" to setOf("executedate", "execute_date", "date", "執行日期"),
        "executetime" to setOf("executetime", "execute_time", "time", "執行時間"),
    )

    fun parseWorkbook(
        workbook: Workbook?,
        defaultExecuteAt: LocalDateTime?,
    ): Pair<List<TruckLaneScheduleExcelPreviewRow>, List<TruckLaneScheduleExcelRowError>> {
        if (workbook == null || workbook.numberOfSheets == 0) {
            return emptyList<TruckLaneScheduleExcelPreviewRow>() to
                listOf(TruckLaneScheduleExcelRowError(0, "Excel 無工作表"))
        }
        val sheet = workbook.getSheetAt(0)
        if (sheet.physicalNumberOfRows < 2) {
            return emptyList<TruckLaneScheduleExcelPreviewRow>() to
                listOf(TruckLaneScheduleExcelRowError(0, "至少需要標題列與一筆資料"))
        }

        val headerRow = sheet.getRow(sheet.firstRowNum) ?: return emptyList<TruckLaneScheduleExcelPreviewRow>() to
            listOf(TruckLaneScheduleExcelRowError(0, "缺少標題列"))

        val colIndex = mutableMapOf<String, Int>()
        for (c in 0 until headerRow.lastCellNum) {
            val cell = headerRow.getCell(c) ?: continue
            val raw = formatter.formatCellValue(cell).trim().lowercase(Locale.ROOT)
            if (raw.isEmpty()) continue
            for ((key, aliases) in headerAliases) {
                if (aliases.any { raw == it || raw.replace(" ", "") == it }) {
                    colIndex[key] = c
                }
            }
        }

        if (!colIndex.containsKey("shop") || !colIndex.containsKey("targetlane")) {
            return emptyList<TruckLaneScheduleExcelPreviewRow>() to listOf(
                TruckLaneScheduleExcelRowError(
                    0,
                    "缺少必要欄位：ShopID/ShopCode 與 TargetLane",
                ),
            )
        }

        val preview = mutableListOf<TruckLaneScheduleExcelPreviewRow>()
        val errors = mutableListOf<TruckLaneScheduleExcelRowError>()
        val firstData = sheet.firstRowNum + 1
        for (r in firstData..sheet.lastRowNum) {
            val row = sheet.getRow(r) ?: continue
            val rowIndex = r + 1
            val shopRaw = cellString(row, colIndex["shop"])
            val laneRaw = cellString(row, colIndex["targetlane"])
            if (shopRaw.isBlank() && laneRaw.isBlank()) continue

            val storeRaw = colIndex["targetstoreid"]?.let { cellString(row, it) }.orEmpty()
            val storeId = truckService.normalizeRouteStoreId(
                if (storeRaw.isBlank()) "2F" else storeRaw,
            )
            val remark = colIndex["targetremark"]?.let { cellString(row, it) }
                ?.trim()?.takeIf { it.isNotEmpty() }

            val executeAt = parseExecuteAt(
                colIndex["executedate"]?.let { cellString(row, it) },
                colIndex["executetime"]?.let { cellString(row, it) },
                defaultExecuteAt,
            )

            if (shopRaw.isBlank()) {
                errors.add(TruckLaneScheduleExcelRowError(rowIndex, "ShopID/ShopCode 不可為空"))
                continue
            }
            if (laneRaw.isBlank()) {
                errors.add(TruckLaneScheduleExcelRowError(rowIndex, "TargetLane 不可為空"))
                continue
            }

            val truck = resolveTruckByShopCode(shopRaw.trim())
            if (truck == null) {
                errors.add(TruckLaneScheduleExcelRowError(rowIndex, "找不到店鋪：$shopRaw"))
                continue
            }

            val toKey = TruckService.TruckLaneBucketKey(
                truckLanceCode = laneRaw.trim(),
                storeId = storeId,
                remark = truckService.bucketRemarkForStore(storeId, remark),
            )
            val validateErr = truckService.validateMoveToLane(truck.id ?: 0L, toKey)
            if (validateErr != null) {
                errors.add(TruckLaneScheduleExcelRowError(rowIndex, validateErr))
                continue
            }

            preview.add(
                TruckLaneScheduleExcelPreviewRow(
                    rowIndex = rowIndex,
                    shopCode = truck.shopCode?.trim().orEmpty(),
                    toTruckLanceCode = toKey.truckLanceCode,
                    toRemark = toKey.remark,
                    toStoreId = toKey.storeId,
                    executeAt = executeAt,
                    truckRowId = truck.id,
                ),
            )
        }
        return preview to errors
    }

    private fun cellString(row: org.apache.poi.ss.usermodel.Row, col: Int?): String {
        if (col == null) return ""
        val cell = row.getCell(col) ?: return ""
        return formatter.formatCellValue(cell).trim()
    }

    private fun resolveTruckByShopCode(shopCode: String): Truck? {
        val code = shopCode.trim()
        val all = truckRepository.findAllForRouteBoard()
        val matches = all.filter { t ->
            !t.deleted &&
                t.shopCode?.trim()?.equals(code, ignoreCase = true) == true
        }
        return matches.minByOrNull { it.id ?: Long.MAX_VALUE }
    }

    private fun parseExecuteAt(
        dateStr: String?,
        timeStr: String?,
        default: LocalDateTime?,
    ): LocalDateTime? {
        if (dateStr.isNullOrBlank() && timeStr.isNullOrBlank()) return default
        val date = parseDate(dateStr) ?: default?.toLocalDate() ?: LocalDate.now()
        val time = parseTime(timeStr) ?: default?.toLocalTime() ?: LocalTime.of(0, 0)
        return LocalDateTime.of(date, time)
    }

    private fun parseDate(s: String?): LocalDate? {
        if (s.isNullOrBlank()) return null
        val t = s.trim()
        for (fmt in listOf("yyyy-MM-dd", "yyyy/MM/dd", "dd/MM/yyyy")) {
            try {
                return LocalDate.parse(t, DateTimeFormatter.ofPattern(fmt))
            } catch (_: Exception) {
            }
        }
        return null
    }

    private fun parseTime(s: String?): LocalTime? {
        if (s.isNullOrBlank()) return null
        val t = s.trim()
        for (fmt in listOf("HH:mm", "HH:mm:ss", "H:mm")) {
            try {
                return LocalTime.parse(t, DateTimeFormatter.ofPattern(fmt))
            } catch (_: Exception) {
            }
        }
        return null
    }
}
