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

import com.ffii.fpsms.modules.pickOrder.entity.Truck
import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleLineAction
import com.ffii.fpsms.modules.pickOrder.web.models.*
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.stereotype.Service
import java.time.LocalTime

@Service
open class TruckLaneSchedulePlanService(
    private val truckService: TruckService,
    private val schedulePlanValidator: TruckLaneSchedulePlanValidator,
) {
    open fun planFromRouteExcel(workbook: Workbook?): RouteExcelSchedulePlanResponse {
        val parsed = truckService.parseRouteLanesExcel(workbook)
        return planFromParsedRows(parsed.rows, parsed.sheetCount)
    }

    open fun planFromParsedRows(
        rows: List<RouteLaneImportPreviewRow>,
        sheetCount: Int = 0,
    ): RouteExcelSchedulePlanResponse {
        val lines = mutableListOf<TruckLaneScheduleLineRequest>()
        val previews = mutableListOf<RouteExcelSchedulePlanPreviewRow>()
        val errors = mutableListOf<RouteExcelSchedulePlanError>()
        val moveTruckIds = mutableSetOf<Long>()
        val deleteTruckIds = mutableSetOf<Long>()
        val ensuredLaneKeys = mutableSetOf<String>()

        if (rows.isEmpty()) {
            return emptyPlan(sheetCount, rows.size)
        }

        val excelShopCodesGlobal =
            rows
                .map { normalizeShopCodeKey(it.shopCode) }
                .filter { it.isNotEmpty() }
                .toSet()

        val rowsByBucket =
            rows.groupBy { row ->
                laneKeyString(truckService.bucketKeyForRouteRow(row))
            }

        for ((_, bucketRows) in rowsByBucket) {
            if (bucketRows.isEmpty()) continue
            val sample = bucketRows.first()
            val bucketKey = truckService.bucketKeyForRouteRow(sample)
            val currentBucket =
                truckService
                    .trucksInLaneBucket(bucketKey)
                    .filter { !truckService.isPlaceholderTruckRow(it) }

            if (currentBucket.isEmpty()) {
                val defaultDeparture = defaultDepartureForRows(bucketRows)
                if (defaultDeparture == null) {
                    errors.add(
                        RouteExcelSchedulePlanError(
                            shopCode = sample.shopCode,
                            shopName = sample.shopName,
                            message = "invalid_departure",
                        ),
                    )
                    continue
                }
                val laneKey = laneKeyString(bucketKey)
                if (ensuredLaneKeys.add(laneKey)) {
                    addLine(
                        lines,
                        previews,
                        TruckLaneScheduleLineRequest(
                            action = TruckLaneScheduleLineAction.ENSURE_LANE,
                            toTruckLanceCode = bucketKey.truckLanceCode,
                            toRemark = bucketKey.remark,
                            toStoreId = bucketKey.storeId,
                            toLoadingSequence = 0,
                            departureTime = defaultDeparture,
                        ),
                    )
                }
            }

            for (row in bucketRows) {
                val toKey = truckService.bucketKeyForRouteRow(row)
                val departure = truckService.parseRouteDepartureTime(row.departureTime)
                if (departure == null) {
                    errors.add(
                        RouteExcelSchedulePlanError(
                            shopCode = row.shopCode,
                            shopName = row.shopName,
                            message = "invalid_departure",
                        ),
                    )
                    continue
                }

                val truckRowId = row.truckRowId
                if (truckRowId == null) {
                    addLine(
                        lines,
                        previews,
                        TruckLaneScheduleLineRequest(
                            action = TruckLaneScheduleLineAction.CREATE,
                            toTruckLanceCode = toKey.truckLanceCode,
                            toRemark = toKey.remark,
                            toStoreId = toKey.storeId,
                            toLoadingSequence = row.loadingSequence.coerceAtLeast(0),
                            toDistrictReference = row.districtReference,
                            shopId = row.shopId,
                            shopCode = row.shopCode,
                            shopName = row.shopName,
                            departureTime = departure,
                            logisticId = row.logisticId,
                        ),
                    )
                    continue
                }

                val truck = truckService.findTruckById(truckRowId)
                if (truck == null) {
                    errors.add(
                        RouteExcelSchedulePlanError(
                            shopCode = row.shopCode,
                            shopName = row.shopName,
                            message = "truck_not_found",
                        ),
                    )
                    continue
                }
                if (truckService.isPlaceholderTruckRow(truck)) {
                    errors.add(
                        RouteExcelSchedulePlanError(
                            shopCode = row.shopCode,
                            shopName = row.shopName,
                            message = "placeholder_row",
                        ),
                    )
                    continue
                }

                if (!truckService.truckNeedsMoveForRouteRow(truck, row)) {
                    continue
                }
                if (!moveTruckIds.add(truckRowId)) continue

                addLine(
                    lines,
                    previews,
                    TruckLaneScheduleLineRequest(
                        action = TruckLaneScheduleLineAction.MOVE,
                        truckRowId = truckRowId,
                        toTruckLanceCode = toKey.truckLanceCode,
                        toRemark = toKey.remark,
                        toStoreId = toKey.storeId,
                        toLoadingSequence = row.loadingSequence.coerceAtLeast(0),
                        toDistrictReference = row.districtReference,
                        shopCode = row.shopCode,
                        shopName = row.shopName,
                        departureTime = departure,
                        logisticId = row.logisticId,
                    ),
                )
            }

            for (truck in currentBucket) {
                val truckRowId = truck.id ?: continue
                if (moveTruckIds.contains(truckRowId) || deleteTruckIds.contains(truckRowId)) {
                    continue
                }
                val shopCodeKey = normalizeShopCodeKey(truck.shopCode)
                if (shopCodeKey.isNotEmpty() && excelShopCodesGlobal.contains(shopCodeKey)) {
                    continue
                }
                if (!deleteTruckIds.add(truckRowId)) continue
                val fromKey = truckService.truckLaneBucketKeyOf(truck)
                addLine(
                    lines,
                    previews,
                    TruckLaneScheduleLineRequest(
                        action = TruckLaneScheduleLineAction.DELETE,
                        truckRowId = truckRowId,
                        toTruckLanceCode = fromKey.truckLanceCode,
                        toRemark = fromKey.remark,
                        toStoreId = fromKey.storeId,
                        shopCode = truck.shopCode,
                        shopName = truck.shopName,
                    ),
                )
            }
        }

        val counts =
            RouteExcelSchedulePlanCounts(
                moves = lines.count { it.action == TruckLaneScheduleLineAction.MOVE },
                creates = lines.count { it.action == TruckLaneScheduleLineAction.CREATE },
                deletes = lines.count { it.action == TruckLaneScheduleLineAction.DELETE },
                ensureLanes = lines.count { it.action == TruckLaneScheduleLineAction.ENSURE_LANE },
            )

        val blocking = schedulePlanValidator.validatePlanLines(lines)
        return RouteExcelSchedulePlanResponse(
            sheetCount = if (sheetCount > 0) sheetCount else rowsByBucket.size,
            rowCount = rows.size,
            lines = lines,
            previews = previews,
            errors = errors,
            blockingErrors = blocking,
            canCreate = blocking.isEmpty() && errors.isEmpty(),
            counts = counts,
        )
    }

    private fun emptyPlan(sheetCount: Int, rowCount: Int): RouteExcelSchedulePlanResponse {
        return RouteExcelSchedulePlanResponse(
            sheetCount = sheetCount,
            rowCount = rowCount,
            lines = emptyList(),
            previews = emptyList(),
            errors = emptyList(),
            blockingErrors = emptyList(),
            canCreate = true,
            counts = RouteExcelSchedulePlanCounts(0, 0, 0, 0),
        )
    }

    private fun addLine(
        lines: MutableList<TruckLaneScheduleLineRequest>,
        previews: MutableList<RouteExcelSchedulePlanPreviewRow>,
        line: TruckLaneScheduleLineRequest,
    ) {
        lines.add(line)
        previews.add(
            RouteExcelSchedulePlanPreviewRow(
                action = line.action.name,
                truckRowId = line.truckRowId,
                shopCode = line.shopCode,
                shopName = line.shopName,
                toTruckLanceCode = line.toTruckLanceCode,
                toRemark = line.toRemark,
                toStoreId = line.toStoreId,
                toLoadingSequence = line.toLoadingSequence,
            ),
        )
    }

    private fun defaultDepartureForRows(rows: List<RouteLaneImportPreviewRow>): LocalTime? {
        for (row in rows) {
            val dep = truckService.parseRouteDepartureTime(row.departureTime)
            if (dep != null) return dep
        }
        return null
    }

    private fun laneKeyString(key: TruckService.TruckLaneBucketKey): String {
        return "${key.truckLanceCode}|${key.storeId}|${key.remark ?: ""}"
    }

    private fun normalizeShopCodeKey(code: String?): String {
        return code?.trim()?.lowercase().orEmpty()
    }
}
