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

import com.ffii.fpsms.modules.pickOrder.entity.*
import com.ffii.fpsms.modules.pickOrder.web.models.*
import org.springframework.transaction.annotation.Transactional
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException
import java.time.LocalDateTime

@Service
open class TruckLaneScheduleService(
    private val scheduleRepository: TruckLaneScheduleRepository,
    private val scheduleLineRepository: TruckLaneScheduleLineRepository,
    private val truckService: TruckService,
    private val excelSupport: TruckLaneScheduleExcelSupport,
    private val scheduleApplier: TruckLaneScheduleApplier,
    private val schedulePlanService: TruckLaneSchedulePlanService,
    private val schedulePlanValidator: TruckLaneSchedulePlanValidator,
    @org.springframework.beans.factory.annotation.Value("\${truck.lane.schedule.lock.hours:24}")
    private val scheduleLockWindowHours: Long = 24,
) {
    @Transactional
    open fun createManual(request: CreateTruckLaneScheduleRequest): TruckLaneScheduleResponse {
        validateExecuteAt(request.executeAt)
        val requestLines = resolveRequestLines(request)
        if (requestLines.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "lines 不可為空")
        }
        val blocking = schedulePlanValidator.validatePlanLines(requestLines)
        if (blocking.isNotEmpty()) {
            throw ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                blocking.joinToString("; ") { "${it.code}:${it.messageKey}" },
            )
        }

        val ensuredLaneKeys =
            requestLines
                .filter { it.action == TruckLaneScheduleLineAction.ENSURE_LANE }
                .map { toBucketKey(it) }
                .toSet()

        val schedule = TruckLaneSchedule().apply {
            executeAt = request.executeAt
            status = TruckLaneScheduleStatus.PENDING
            source = TruckLaneScheduleSource.MANUAL
            note = request.note?.trim()
        }

        val lines = mutableListOf<TruckLaneScheduleLine>()
        for (req in requestLines) {
            lines.add(buildScheduleLine(req, ensuredLaneKeys))
        }

        val savedHeader = scheduleRepository.save(schedule)
        for (line in lines) {
            line.schedule = savedHeader
            scheduleLineRepository.save(line)
        }
        return toResponse(savedHeader, includeLines = true)
    }

    open fun planFromRouteExcel(workbook: org.apache.poi.ss.usermodel.Workbook?): RouteExcelSchedulePlanResponse {
        return schedulePlanService.planFromRouteExcel(workbook)
    }

    @Transactional
    open fun createFromExcelRows(
        rows: List<TruckLaneScheduleExcelPreviewRow>,
        defaultExecuteAt: LocalDateTime,
        note: String?,
    ): List<TruckLaneScheduleResponse> {
        if (rows.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無有效資料列")
        }
        val grouped = rows.groupBy { it.executeAt ?: defaultExecuteAt }
        return grouped.map { (executeAt, group) ->
            createManual(
                CreateTruckLaneScheduleRequest(
                    executeAt = executeAt,
                    note = note,
                    lines = group.mapNotNull { r ->
                        val id = r.truckRowId ?: return@mapNotNull null
                        val storeNorm = truckService.normalizeRouteStoreId(r.toStoreId)
                        val toKey = TruckService.TruckLaneBucketKey(
                            truckLanceCode = r.toTruckLanceCode.trim(),
                            storeId = storeNorm,
                            remark = truckService.bucketRemarkForStore(storeNorm, r.toRemark),
                        )
                        TruckLaneScheduleLineRequest(
                            action = TruckLaneScheduleLineAction.MOVE,
                            truckRowId = id,
                            toTruckLanceCode = r.toTruckLanceCode,
                            toRemark = r.toRemark,
                            toStoreId = r.toStoreId,
                            toLoadingSequence = defaultLoadingSequenceForMove(id, toKey),
                        )
                    },
                ),
            )
        }
    }

    open fun list(
        statuses: List<TruckLaneScheduleStatus>?,
        limit: Int = 50,
    ): List<TruckLaneScheduleResponse> {
        val list =
            if (statuses.isNullOrEmpty()) {
                scheduleRepository.findAllByDeletedFalseAndStatusInOrderByExecuteAtDesc(
                    TruckLaneScheduleStatus.entries.toList(),
                )
            } else {
                scheduleRepository.findAllByDeletedFalseAndStatusInOrderByExecuteAtDesc(statuses)
            }
        val taken = list.take(limit)
        val ids = taken.mapNotNull { it.id }
        val linesByScheduleId =
            if (ids.isEmpty()) {
                emptyMap()
            } else {
                scheduleLineRepository
                    .findAllBySchedule_IdInAndDeletedFalseOrderBySchedule_IdAscIdAsc(ids)
                    .groupBy { it.schedule?.id ?: 0L }
            }
        return taken.map { s ->
            toResponse(
                s,
                includeLines = false,
                preloadedLines = linesByScheduleId[s.id ?: 0L],
            )
        }
    }

    open fun getById(id: Long): TruckLaneScheduleResponse {
        val s = scheduleRepository.findByIdAndDeletedFalse(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
        return toResponse(s, includeLines = true)
    }

    open fun pendingTruckRowIds(): PendingTruckRowIdsResponse {
        val openStatuses = listOf(TruckLaneScheduleStatus.PENDING, TruckLaneScheduleStatus.APPLYING)
        return PendingTruckRowIdsResponse(
            truckRowIds = scheduleLineRepository.findPendingTruckRowIds(
                TruckLaneScheduleLineStatus.PENDING,
                openStatuses,
            ),
            lockedTruckRowIds = scheduleLineRepository.findLockingPendingTruckRowIds(
                TruckLaneScheduleLineStatus.PENDING,
                openStatuses,
                TruckLaneScheduleStatus.APPLYING,
                LocalDateTime.now().plusHours(scheduleLockWindowHours),
            ),
        )
    }

    @Transactional
    open fun cancel(id: Long): TruckLaneScheduleResponse {
        val s = scheduleRepository.findByIdAndDeletedFalse(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
        if (s.status != TruckLaneScheduleStatus.PENDING) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 PENDING 可取消")
        }
        s.status = TruckLaneScheduleStatus.CANCELLED
        val lineList = scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L)
        lineList.forEach { it.lineStatus = TruckLaneScheduleLineStatus.SKIPPED }
        scheduleLineRepository.saveAll(lineList)
        return toResponse(scheduleRepository.save(s), includeLines = true)
    }

    open fun applyNow(id: Long): TruckLaneScheduleResponse {
        return when (scheduleApplier.applyOne(id)) {
            ScheduleApplyOutcome.NOT_FOUND ->
                throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
            ScheduleApplyOutcome.SKIPPED -> {
                val current = scheduleRepository.findByIdAndDeletedFalse(id)
                val msg =
                    when (current?.status) {
                        TruckLaneScheduleStatus.APPLYING ->
                            "排程正在執行中，請稍後再試"
                        TruckLaneScheduleStatus.PENDING ->
                            "無法認領排程（可能已被其他程序處理）"
                        else -> "僅 PENDING 可立即執行"
                    }
                throw ResponseStatusException(HttpStatus.CONFLICT, msg)
            }
            ScheduleApplyOutcome.FAILED,
            ScheduleApplyOutcome.APPLIED,
            -> getById(id)
        }
    }

    @Transactional
    open fun retryFailed(
        id: Long,
        executeAt: LocalDateTime?,
    ): TruckLaneScheduleResponse {
        val s = scheduleRepository.findByIdAndDeletedFalse(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
        if (s.status == TruckLaneScheduleStatus.PARTIAL) {
            throw ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                "PARTIAL 排程不可重試，請先還原看板後重新建立排程",
            )
        }
        if (s.status != TruckLaneScheduleStatus.FAILED) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 FAILED 可重試失敗列")
        }
        val lines =
            scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L)
        val retryLines =
            lines.filter {
                it.lineStatus == TruckLaneScheduleLineStatus.FAILED ||
                    it.lineStatus == TruckLaneScheduleLineStatus.PENDING
            }
        if (retryLines.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重試的失敗列")
        }
        val whenAt = executeAt ?: LocalDateTime.now().plusMinutes(1)
        validateExecuteAt(whenAt)
        val requestLines =
            retryLines.mapNotNull { line -> lineToRequest(line) }
        if (requestLines.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重試的失敗列")
        }
        val created = createManual(
            CreateTruckLaneScheduleRequest(
                executeAt = whenAt,
                note = "retry from schedule #$id",
                lines = requestLines,
            ),
        )
        // 重試排程已接手追蹤，原 FAILED 單標為 IGNORED 以免失敗徽章長亮。
        s.status = TruckLaneScheduleStatus.IGNORED
        s.errorMessage =
            ((s.errorMessage?.let { "$it；" } ?: "") + "已由重試排程 #${created.id} 取代").take(2000)
        scheduleRepository.save(s)
        return created
    }

    @Transactional
    open fun reactivateCancelled(
        id: Long,
        executeAt: LocalDateTime?,
    ): TruckLaneScheduleResponse {
        val s = scheduleRepository.findByIdAndDeletedFalse(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
        if (s.status != TruckLaneScheduleStatus.CANCELLED) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 CANCELLED 可重新排程")
        }
        val lines =
            scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L)
        val reactivatable =
            lines.filter { it.lineStatus == TruckLaneScheduleLineStatus.SKIPPED }
        if (reactivatable.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重新排程的明細")
        }
        val whenAt = executeAt ?: LocalDateTime.now().plusMinutes(1)
        validateExecuteAt(whenAt)
        val requestLines = reactivatable.mapNotNull { line -> lineToRequest(line) }
        if (requestLines.isEmpty()) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重新排程的明細")
        }
        val blocking = schedulePlanValidator.validatePlanLines(requestLines)
        if (blocking.isNotEmpty()) {
            throw ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                blocking.joinToString("; ") { "${it.code}:${it.messageKey}" },
            )
        }
        s.executeAt = whenAt
        s.status = TruckLaneScheduleStatus.PENDING
        s.appliedAt = null
        s.errorMessage = null
        for (line in reactivatable) {
            line.lineStatus = TruckLaneScheduleLineStatus.PENDING
            line.errorMessage = null
            line.appliedAt = null
        }
        scheduleLineRepository.saveAll(reactivatable)
        return toResponse(scheduleRepository.save(s), includeLines = true)
    }

    @Transactional
    open fun ignore(id: Long): TruckLaneScheduleResponse {
        val s = scheduleRepository.findByIdAndDeletedFalse(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在")
        if (s.status == TruckLaneScheduleStatus.IGNORED) {
            return toResponse(s, includeLines = true)
        }
        if (s.status == TruckLaneScheduleStatus.CANCELLED) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "已取消的排程無法忽略")
        }
        val lines =
            scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L)
        val failedLineCount =
            lines.count { it.lineStatus == TruckLaneScheduleLineStatus.FAILED }
        val canIgnore =
            s.status == TruckLaneScheduleStatus.FAILED ||
                s.status == TruckLaneScheduleStatus.PARTIAL ||
                failedLineCount > 0
        if (!canIgnore) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅含失敗明細的排程可忽略")
        }
        s.status = TruckLaneScheduleStatus.IGNORED
        return toResponse(scheduleRepository.save(s), includeLines = true)
    }

    open fun applyDueSchedules(batchLimit: Int = 20) {
        reaperStaleApplying()
        val due = scheduleRepository.findDuePending(
            LocalDateTime.now(),
            TruckLaneScheduleStatus.PENDING,
        ).take(batchLimit)
        for (schedule in due) {
            val scheduleId = schedule.id ?: continue
            try {
                scheduleApplier.applyOne(scheduleId)
            } catch (e: Exception) {
                scheduleApplier.markFailed(scheduleId, e)
            }
        }
    }

    @Transactional
    open fun reaperStaleApplying(staleMinutes: Long = 10) {
        val staleBefore = LocalDateTime.now().minusMinutes(staleMinutes)
        val stale =
            scheduleRepository.findStaleApplying(
                TruckLaneScheduleStatus.APPLYING,
                staleBefore,
            )
        for (schedule in stale) {
            schedule.status = TruckLaneScheduleStatus.FAILED
            schedule.errorMessage =
                "排程執行逾時（>${staleMinutes} 分鐘仍為 APPLYING），請依 runbook 手動還原".take(2000)
            scheduleRepository.save(schedule)
        }
    }

    open fun parseExcel(
        workbook: org.apache.poi.ss.usermodel.Workbook?,
        defaultExecuteAt: LocalDateTime?,
    ): ParseTruckLaneScheduleExcelResponse {
        val (rows, errors) = excelSupport.parseWorkbook(workbook, defaultExecuteAt)
        return ParseTruckLaneScheduleExcelResponse(
            rowCount = rows.size + errors.size,
            validCount = rows.size,
            errorCount = errors.size,
            rows = rows,
            errors = errors,
        )
    }

    private fun resolveRequestLines(request: CreateTruckLaneScheduleRequest): List<TruckLaneScheduleLineRequest> {
        if (!request.lines.isNullOrEmpty()) return request.lines
        return request.moves.orEmpty().map { move ->
            TruckLaneScheduleLineRequest(
                action = TruckLaneScheduleLineAction.MOVE,
                truckRowId = move.truckRowId,
                toTruckLanceCode = move.toTruckLanceCode,
                toRemark = move.toRemark,
                toStoreId = move.toStoreId,
                toLoadingSequence = move.toLoadingSequence,
                toDistrictReference = move.toDistrictReference,
            )
        }
    }

    private fun buildScheduleLine(
        req: TruckLaneScheduleLineRequest,
        ensuredLaneKeys: Set<TruckService.TruckLaneBucketKey>,
    ): TruckLaneScheduleLine {
        val toKey = toBucketKey(req)
        val allowEmptyTarget = ensuredLaneKeys.contains(toKey)

        return when (req.action) {
            TruckLaneScheduleLineAction.ENSURE_LANE -> {
                val bucket = truckService.trucksInLaneBucket(toKey)
                if (bucket.isNotEmpty()) {
                    throw ResponseStatusException(
                        HttpStatus.BAD_REQUEST,
                        "車線 ${toKey.truckLanceCode} 已存在，無需 ENSURE_LANE",
                    )
                }
                val departure = req.departureTime
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "ENSURE_LANE 缺少 departureTime")
                TruckLaneScheduleLine().apply {
                    lineAction = TruckLaneScheduleLineAction.ENSURE_LANE
                    toTruckLanceCode = toKey.truckLanceCode
                    toRemark = toKey.remark
                    toStoreId = toKey.storeId
                    toLoadingSequence = req.toLoadingSequence ?: 0
                    this.departureTime = departure
                    logisticId = req.logisticId
                    lineStatus = TruckLaneScheduleLineStatus.PENDING
                }
            }

            TruckLaneScheduleLineAction.CREATE -> {
                val shopId = req.shopId
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "CREATE 缺少 shopId")
                val departure = req.departureTime
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "CREATE 缺少 departureTime")
                val bucket = truckService.trucksInLaneBucket(toKey)
                if (bucket.isEmpty() && !allowEmptyTarget) {
                    throw ResponseStatusException(
                        HttpStatus.BAD_REQUEST,
                        "目標車線 ${toKey.truckLanceCode} 不存在，需先 ENSURE_LANE",
                    )
                }
                val seq = req.toLoadingSequence ?: 0
                TruckLaneScheduleLine().apply {
                    lineAction = TruckLaneScheduleLineAction.CREATE
                    this.shopId = shopId
                    shopCode = req.shopCode
                    shopName = req.shopName
                    toTruckLanceCode = toKey.truckLanceCode
                    toRemark = toKey.remark
                    toStoreId = toKey.storeId
                    toLoadingSequence = seq
                    toDistrictReference = req.toDistrictReference?.trim()?.takeIf { it.isNotEmpty() }
                    this.departureTime = departure
                    logisticId = req.logisticId
                    lineStatus = TruckLaneScheduleLineStatus.PENDING
                }
            }

            TruckLaneScheduleLineAction.DELETE -> {
                val truckRowId = req.truckRowId
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "DELETE 缺少 truckRowId")
                assertNoOpenPending(truckRowId)
                val truck = truckService.findTruckById(truckRowId)
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "找不到 truck #$truckRowId")
                if (truckService.isPlaceholderTruckRow(truck)) {
                    throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無法刪除空白占位列")
                }
                val fromKey = truckService.truckLaneBucketKeyOf(truck)
                TruckLaneScheduleLine().apply {
                    lineAction = TruckLaneScheduleLineAction.DELETE
                    this.truckRowId = truckRowId
                    shopCode = truck.shopCode
                    shopName = truck.shopName
                    fromTruckLanceCode = fromKey.truckLanceCode
                    fromRemark = fromKey.remark
                    fromStoreId = fromKey.storeId
                    toTruckLanceCode = fromKey.truckLanceCode
                    toRemark = fromKey.remark
                    toStoreId = fromKey.storeId
                    lineStatus = TruckLaneScheduleLineStatus.PENDING
                }
            }

            TruckLaneScheduleLineAction.MOVE -> {
                val truckRowId = req.truckRowId
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "MOVE 缺少 truckRowId")
                assertNoOpenPending(truckRowId)
                val truck = truckService.findTruckById(truckRowId)
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "找不到 truck #$truckRowId")
                val fromKey = truckService.truckLaneBucketKeyOf(truck)
                val err = truckService.validateMoveToLane(truckRowId, toKey, allowEmptyTarget)
                if (err != null) {
                    throw ResponseStatusException(HttpStatus.BAD_REQUEST, "truck #$truckRowId: $err")
                }
                val seq = req.toLoadingSequence
                    ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "MOVE 缺少 toLoadingSequence")
                TruckLaneScheduleLine().apply {
                    lineAction = TruckLaneScheduleLineAction.MOVE
                    this.truckRowId = truckRowId
                    shopCode = truck.shopCode
                    shopName = truck.shopName
                    fromTruckLanceCode = fromKey.truckLanceCode
                    fromRemark = fromKey.remark
                    fromStoreId = fromKey.storeId
                    toTruckLanceCode = toKey.truckLanceCode
                    toRemark = toKey.remark
                    toStoreId = toKey.storeId
                    toDistrictReference =
                        req.toDistrictReference?.trim()?.takeIf { it.isNotEmpty() }
                            ?: truck.districtReference
                    toLoadingSequence = seq
                    departureTime = req.departureTime
                    logisticId = req.logisticId
                    lineStatus = TruckLaneScheduleLineStatus.PENDING
                }
            }
        }
    }

    private fun assertNoOpenPending(truckRowId: Long) {
        if (scheduleLineRepository.countOpenPendingForTruckRow(
                truckRowId,
                TruckLaneScheduleLineStatus.PENDING,
                TruckLaneScheduleLockSupport.openScheduleStatuses,
                null,
            ) > 0
        ) {
            throw ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                "truck #$truckRowId 已有待執行排程",
            )
        }
    }

    private fun lineToRequest(line: TruckLaneScheduleLine): TruckLaneScheduleLineRequest? {
        val action = line.lineAction
        val toCode = line.toTruckLanceCode?.trim().orEmpty()
        val toStore = line.toStoreId?.trim().orEmpty()
        if (toCode.isEmpty() || toStore.isEmpty()) return null
        return TruckLaneScheduleLineRequest(
            action = action,
            truckRowId = line.truckRowId,
            toTruckLanceCode = toCode,
            toRemark = line.toRemark,
            toStoreId = toStore,
            toLoadingSequence = line.toLoadingSequence,
            toDistrictReference = line.toDistrictReference,
            shopId = line.shopId,
            shopCode = line.shopCode,
            shopName = line.shopName,
            departureTime = line.departureTime,
            logisticId = line.logisticId,
        )
    }

    private fun toBucketKey(req: TruckLaneScheduleLineRequest): TruckService.TruckLaneBucketKey {
        val storeNorm = truckService.normalizeRouteStoreId(req.toStoreId)
        return TruckService.TruckLaneBucketKey(
            truckLanceCode = req.toTruckLanceCode.trim(),
            storeId = storeNorm,
            remark = truckService.bucketRemarkForStore(storeNorm, req.toRemark),
        )
    }

    private fun validateExecuteAt(executeAt: LocalDateTime) {
        val min = LocalDateTime.now().minusMinutes(2)
        if (executeAt.isBefore(min)) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "執行時間不可早於目前時間")
        }
    }

    private fun toResponse(
        s: TruckLaneSchedule,
        includeLines: Boolean,
        preloadedLines: List<TruckLaneScheduleLine>? = null,
    ): TruckLaneScheduleResponse {
        val lines =
            preloadedLines
                ?: scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(
                    s.id ?: 0L,
                )
        val counts = if (lines.isNotEmpty()) {
            TruckLaneScheduleLineCounts(
                total = lines.size,
                applied = lines.count { it.lineStatus == TruckLaneScheduleLineStatus.APPLIED },
                failed = lines.count { it.lineStatus == TruckLaneScheduleLineStatus.FAILED },
                pending = lines.count { it.lineStatus == TruckLaneScheduleLineStatus.PENDING },
            )
        } else {
            null
        }
        return TruckLaneScheduleResponse(
            id = s.id ?: 0L,
            executeAt = s.executeAt?.toString().orEmpty(),
            status = s.status.name,
            source = s.source.name,
            note = s.note,
            appliedAt = s.appliedAt?.toString(),
            errorMessage = s.errorMessage,
            snapshotVersionId = s.snapshotVersionId,
            preApplySnapshotVersionId = s.preApplySnapshotVersionId,
            created = s.created?.toString(),
            createdBy = s.createdBy,
            modifiedBy = s.modifiedBy,
            lines = if (includeLines) lines.map { toLineResponse(it) } else null,
            lineCounts = counts,
        )
    }

    private fun defaultLoadingSequenceForMove(
        truckRowId: Long,
        to: TruckService.TruckLaneBucketKey,
    ): Int {
        val truck = truckService.findTruckById(truckRowId)
        val district = truck?.districtReference?.trim().orEmpty()
        val bucket = truckService.trucksInLaneBucket(to)
        val maxSeq =
            bucket
                .filter { t ->
                    val d = t.districtReference?.trim().orEmpty()
                    district.isEmpty() || d == district
                }
                .maxOfOrNull { it.loadingSequence ?: 0 } ?: 0
        return maxSeq + 1
    }

    private fun toLineResponse(l: TruckLaneScheduleLine): TruckLaneScheduleLineResponse {
        var fromLoadingSequence: Int? = null
        var fromDistrictReference: String? = null
        var fromDepartureTime: String? = null
        val truckRowId = l.truckRowId
        if (l.lineStatus == TruckLaneScheduleLineStatus.PENDING && truckRowId != null) {
            truckService.findTruckById(truckRowId)?.let { truck ->
                fromLoadingSequence = truck.loadingSequence
                fromDistrictReference = truck.districtReference
                fromDepartureTime = truck.departureTime?.toString()
            }
        }
        return TruckLaneScheduleLineResponse(
            id = l.id ?: 0L,
            action = l.lineAction.name,
            truckRowId = l.truckRowId,
            shopCode = l.shopCode,
            shopName = l.shopName,
            fromTruckLanceCode = l.fromTruckLanceCode,
            fromRemark = l.fromRemark,
            fromStoreId = l.fromStoreId,
            fromLoadingSequence = fromLoadingSequence,
            fromDistrictReference = fromDistrictReference,
            fromDepartureTime = fromDepartureTime,
            toTruckLanceCode = l.toTruckLanceCode.orEmpty(),
            toRemark = l.toRemark,
            toStoreId = l.toStoreId.orEmpty(),
            toDistrictReference = l.toDistrictReference,
            toLoadingSequence = l.toLoadingSequence,
            departureTime = l.departureTime?.toString(),
            lineStatus = l.lineStatus.name,
            errorMessage = l.errorMessage,
            appliedAt = l.appliedAt?.toString(),
        )
    }
}
