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

import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleLineAction
import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleLineRepository
import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleLineStatus
import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleStatus
import com.ffii.fpsms.modules.pickOrder.web.models.SchedulePlanBlockingError
import com.ffii.fpsms.modules.pickOrder.web.models.TruckLaneScheduleLineRequest
import org.springframework.stereotype.Component

@Component
class TruckLaneSchedulePlanValidator(
    private val truckService: TruckService,
    private val scheduleLineRepository: TruckLaneScheduleLineRepository,
) {
    fun validatePlanLines(lines: List<TruckLaneScheduleLineRequest>): List<SchedulePlanBlockingError> {
        val errors = mutableListOf<SchedulePlanBlockingError>()
        errors.addAll(validateR1MoveDeleteOverlap(lines))
        errors.addAll(validateR2DuplicateTruckRow(lines))
        errors.addAll(validateR3OpenScheduleOverlap(lines))
        errors.addAll(validateR4OrphanMoveDelete(lines))
        errors.addAll(validateR5DuplicateCreate(lines))
        return errors
    }

    private fun validateR1MoveDeleteOverlap(
        lines: List<TruckLaneScheduleLineRequest>,
    ): List<SchedulePlanBlockingError> {
        val moveIds =
            lines
                .filter { it.action == TruckLaneScheduleLineAction.MOVE }
                .mapNotNull { it.truckRowId }
                .toSet()
        val deleteIds =
            lines
                .filter { it.action == TruckLaneScheduleLineAction.DELETE }
                .mapNotNull { it.truckRowId }
                .toSet()
        return moveIds.intersect(deleteIds).map { id ->
            SchedulePlanBlockingError(
                code = "R1_MOVE_DELETE_OVERLAP",
                shopCode = lines.firstOrNull { it.truckRowId == id }?.shopCode,
                shopName = lines.firstOrNull { it.truckRowId == id }?.shopName,
                truckRowId = id,
                messageKey = "schedule.plan.r1_move_delete_overlap",
            )
        }
    }

    private fun validateR2DuplicateTruckRow(
        lines: List<TruckLaneScheduleLineRequest>,
    ): List<SchedulePlanBlockingError> {
        val byTruck =
            lines
                .filter { it.truckRowId != null && it.action != TruckLaneScheduleLineAction.ENSURE_LANE }
                .groupBy { it.truckRowId!! }
        return byTruck
            .filter { (_, group) -> group.size > 1 }
            .flatMap { (id, group) ->
                group.map {
                    SchedulePlanBlockingError(
                        code = "R2_DUPLICATE_TRUCK_ROW",
                        shopCode = it.shopCode,
                        shopName = it.shopName,
                        truckRowId = id,
                        messageKey = "schedule.plan.r2_duplicate_truck_row",
                    )
                }
            }
    }

    private fun validateR3OpenScheduleOverlap(
        lines: List<TruckLaneScheduleLineRequest>,
    ): List<SchedulePlanBlockingError> {
        val errors = mutableListOf<SchedulePlanBlockingError>()
        for (line in lines) {
            val truckRowId = line.truckRowId ?: continue
            if (scheduleLineRepository.countOpenPendingForTruckRow(
                    truckRowId,
                    TruckLaneScheduleLineStatus.PENDING,
                    TruckLaneScheduleLockSupport.openScheduleStatuses,
                    null,
                ) > 0
            ) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R3_OPEN_SCHEDULE_OVERLAP",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = truckRowId,
                        messageKey = "schedule.plan.r3_open_schedule_overlap",
                    ),
                )
            }
        }
        return errors
    }

    /**
     * R5：CREATE 重複防護。
     * - 同一計畫內：同店鋪 + 同目標車線桶不可重複 CREATE
     * - 目標車線已有該店鋪的有效列
     * - 其他開放排程已有同店鋪 + 同目標車線桶的 PENDING CREATE
     */
    private fun validateR5DuplicateCreate(
        lines: List<TruckLaneScheduleLineRequest>,
    ): List<SchedulePlanBlockingError> {
        val errors = mutableListOf<SchedulePlanBlockingError>()
        val seenInPlan = mutableSetOf<String>()
        for (line in lines) {
            if (line.action != TruckLaneScheduleLineAction.CREATE) continue
            val shopId = line.shopId ?: continue
            val storeNorm = truckService.normalizeRouteStoreId(line.toStoreId)
            val toKey = TruckService.TruckLaneBucketKey(
                truckLanceCode = line.toTruckLanceCode.trim(),
                storeId = storeNorm,
                remark = truckService.bucketRemarkForStore(storeNorm, line.toRemark),
            )
            val planKey = "$shopId|${toKey.truckLanceCode}|${toKey.storeId}|${toKey.remark ?: ""}"
            if (!seenInPlan.add(planKey)) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R5_DUPLICATE_CREATE_IN_PLAN",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = null,
                        messageKey = "schedule.plan.r5_duplicate_create_in_plan",
                    ),
                )
                continue
            }
            if (truckService.trucksInLaneBucket(toKey).any { it.shop?.id == shopId }) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R5_SHOP_ALREADY_IN_LANE",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = null,
                        messageKey = "schedule.plan.r5_shop_already_in_lane",
                    ),
                )
                continue
            }
            if (scheduleLineRepository.countOpenPendingCreateForShopInBucket(
                    shopId,
                    toKey.truckLanceCode,
                    toKey.storeId,
                    TruckLaneScheduleLineAction.CREATE,
                    TruckLaneScheduleLineStatus.PENDING,
                    TruckLaneScheduleLockSupport.openScheduleStatuses,
                ) > 0
            ) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R5_OPEN_CREATE_OVERLAP",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = null,
                        messageKey = "schedule.plan.r5_open_create_overlap",
                    ),
                )
            }
        }
        return errors
    }

    private fun validateR4OrphanMoveDelete(
        lines: List<TruckLaneScheduleLineRequest>,
    ): List<SchedulePlanBlockingError> {
        val errors = mutableListOf<SchedulePlanBlockingError>()
        for (line in lines) {
            if (line.action != TruckLaneScheduleLineAction.MOVE &&
                line.action != TruckLaneScheduleLineAction.DELETE
            ) {
                continue
            }
            val truckRowId = line.truckRowId
            if (truckRowId == null) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R4_ORPHAN_LINE",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = null,
                        messageKey = "schedule.plan.r4_orphan_line",
                    ),
                )
                continue
            }
            val truck = truckService.findTruckById(truckRowId)
            if (truck == null) {
                errors.add(
                    SchedulePlanBlockingError(
                        code = "R4_TRUCK_NOT_FOUND",
                        shopCode = line.shopCode,
                        shopName = line.shopName,
                        truckRowId = truckRowId,
                        messageKey = "schedule.plan.r4_truck_not_found",
                    ),
                )
            }
        }
        return errors
    }
}
