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

import com.ffii.fpsms.modules.logistic.entity.LogisticRepository
import com.ffii.fpsms.modules.pickOrder.entity.*
import com.ffii.fpsms.modules.pickOrder.web.models.LogisticMasterDiffLine
import com.ffii.fpsms.modules.pickOrder.web.models.*
import jakarta.transaction.Transactional
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException
import java.time.LocalTime

@Service
open class TruckLaneVersionService(
    private val truckRepository: TruckRepository,
    private val truckLaneVersionRepository: TruckLaneVersionRepository,
    private val truckLaneVersionLineRepository: TruckLaneVersionLineRepository,
    private val logisticRepository: LogisticRepository,
) {
    private fun toResponse(v: TruckLaneVersion): TruckLaneVersionResponse =
        TruckLaneVersionResponse(
            id = v.id ?: 0,
            truckLanceCode = v.truckLanceCode ?: "",
            note = v.note,
            created = v.created?.toString(),
            modifiedBy = v.modifiedBy,
        )

    /**
     * 全看板 snapshot：`TruckLaneVersion.truckLanceCode` 為空（建立 snapshot 時未指定單線）。
     * 另：若 line 上出現多種 `truckLanceCode`，視為全看板誤標成單線的舊資料，仍應對「整個 findAllForRouteBoard」做 extras 軟刪。
     */
    private fun isFullBoardSnapshot(
        version: TruckLaneVersion,
        lines: List<TruckLaneVersionLine>,
    ): Boolean {
        if (version.truckLanceCode.isNullOrBlank()) return true
        val distinctLaneCodes =
            lines.mapNotNull { it.truckLanceCode?.trim()?.takeIf { c -> c.isNotEmpty() } }.distinct()
        return distinctLaneCodes.size > 1
    }

    @Transactional
    open fun createSnapshot(request: CreateTruckLaneSnapshotRequest): TruckLaneVersionResponse {
        val lane = request.truckLanceCode?.trim()?.takeIf { it.isNotEmpty() }

        val version = TruckLaneVersion().apply {
            this.truckLanceCode = lane
            this.note = request.note?.trim()
        }
        val savedVersion = truckLaneVersionRepository.save(version)

        val rows =
            if (lane != null) {
                truckRepository.findAllByTruckLanceCodeAndDeletedFalse(lane)
            } else {
                truckRepository.findAllForRouteBoard()
            }
        val lines = rows.map { t ->
            TruckLaneVersionLine().apply {
                this.truckLaneVersion = savedVersion
                this.truckRowId = t.id
                this.truckLanceCode = t.truckLanceCode
                this.shopCode = t.shopCode
                this.branchName = t.shopName
                this.districtReference = t.districtReference
                this.loadingSequence = t.loadingSequence
                this.departureTime = t.departureTime?.toString()
                this.storeId = t.storeId?.trim()?.takeIf { it.isNotEmpty() } ?: "-"
                this.remark = t.remark
                this.logisticId = t.logistic?.id
            }
        }
        if (lines.isNotEmpty()) {
            truckLaneVersionLineRepository.saveAll(lines)
        }

        return toResponse(savedVersion)
    }

    open fun listVersionsByLane(truckLanceCode: String): List<TruckLaneVersionResponse> {
        val lane = truckLanceCode.trim()
        return truckLaneVersionRepository
            .findAllByTruckLanceCodeAndDeletedFalseOrderByCreatedDesc(lane)
            .map(::toResponse)
    }

    open fun listAllVersions(): List<TruckLaneVersionResponse> {
        return truckLaneVersionRepository
            .findAllByDeletedFalseOrderByCreatedDesc()
            .map(::toResponse)
    }

    open fun getVersionLines(versionId: Long): List<TruckLaneVersionLineResponse> {
        return truckLaneVersionLineRepository
            .findAllByTruckLaneVersion_IdAndDeletedFalseOrderByIdAsc(versionId)
            .map {
                TruckLaneVersionLineResponse(
                    truckRowId = it.truckRowId ?: 0,
                    truckLanceCode = it.truckLanceCode,
                    shopCode = it.shopCode,
                    branchName = it.branchName,
                    districtReference = it.districtReference,
                    loadingSequence = it.loadingSequence,
                    departureTime = it.departureTime,
                    storeId = it.storeId ?: "",
                    remark = it.remark,
                    logisticId = it.logisticId,
                )
            }
    }

    open fun diff(fromVersionId: Long, toVersionId: Long): TruckLaneVersionDiffResponse {
        val fromLines = truckLaneVersionLineRepository
            .findAllByTruckLaneVersion_IdAndDeletedFalseOrderByIdAsc(fromVersionId)
        val toLines = truckLaneVersionLineRepository
            .findAllByTruckLaneVersion_IdAndDeletedFalseOrderByIdAsc(toVersionId)

        val fromByRow = fromLines.associateBy { it.truckRowId ?: -1 }
        val toByRow = toLines.associateBy { it.truckRowId ?: -1 }

        val allKeys = (fromByRow.keys + toByRow.keys).filter { it > 0 }.sorted()
        val changed = mutableListOf<TruckLaneVersionDiffLine>()

        fun s(v: Any?): String? = v?.toString()

        allKeys.forEach { key ->
            val a = fromByRow[key]
            val b = toByRow[key]
            val changes = mutableListOf<DiffFieldChange>()

            if (s(a?.truckLanceCode) != s(b?.truckLanceCode)) changes.add(DiffFieldChange("truckLanceCode", s(a?.truckLanceCode), s(b?.truckLanceCode)))
            if (s(a?.shopCode) != s(b?.shopCode)) changes.add(DiffFieldChange("shopCode", s(a?.shopCode), s(b?.shopCode)))
            if (s(a?.branchName) != s(b?.branchName)) changes.add(DiffFieldChange("branchName", s(a?.branchName), s(b?.branchName)))
            if (s(a?.districtReference) != s(b?.districtReference)) changes.add(DiffFieldChange("districtReference", s(a?.districtReference), s(b?.districtReference)))
            if (s(a?.loadingSequence) != s(b?.loadingSequence)) changes.add(DiffFieldChange("loadingSequence", s(a?.loadingSequence), s(b?.loadingSequence)))
            if (s(a?.departureTime) != s(b?.departureTime)) changes.add(DiffFieldChange("departureTime", s(a?.departureTime), s(b?.departureTime)))
            if (s(a?.storeId) != s(b?.storeId)) changes.add(DiffFieldChange("storeId", s(a?.storeId), s(b?.storeId)))
            if (s(a?.remark) != s(b?.remark)) changes.add(DiffFieldChange("remark", s(a?.remark), s(b?.remark)))
            if (s(a?.logisticId) != s(b?.logisticId)) changes.add(DiffFieldChange("logisticId", s(a?.logisticId), s(b?.logisticId)))

            if (changes.isNotEmpty()) {
                changed.add(
                    TruckLaneVersionDiffLine(
                        truckRowId = key,
                        shopCode = b?.shopCode ?: a?.shopCode,
                        changes = changes,
                        truckLanceCode = b?.truckLanceCode ?: a?.truckLanceCode,
                        remark = b?.remark ?: a?.remark,
                    )
                )
            }
        }

        val fromV = truckLaneVersionRepository.findByIdAndDeletedFalse(fromVersionId)
        val toV = truckLaneVersionRepository.findByIdAndDeletedFalse(toVersionId)
        val logisticMasterChanges =
            if (fromV != null && toV != null) {
                diffLogisticMastersBetweenVersions(fromV, toV)
            } else {
                emptyList()
            }

        return TruckLaneVersionDiffResponse(
            fromVersionId = fromVersionId,
            toVersionId = toVersionId,
            changed = changed,
            logisticMasterChanges = logisticMasterChanges,
        )
    }

    /**
     * 物流主檔在兩個版本快照時間之間的新增/修改（含尚未指派到任何 truck 列者）。
     */
    private fun diffLogisticMastersBetweenVersions(
        fromVersion: TruckLaneVersion,
        toVersion: TruckLaneVersion,
    ): List<LogisticMasterDiffLine> {
        val fromAt = fromVersion.created ?: return emptyList()
        val toAt = toVersion.created ?: return emptyList()
        if (!toAt.isAfter(fromAt)) return emptyList()

        fun inOpenInterval(ts: java.time.LocalDateTime?): Boolean {
            if (ts == null) return false
            return ts.isAfter(fromAt) && !ts.isAfter(toAt)
        }

        val out = ArrayList<LogisticMasterDiffLine>()
        for (l in logisticRepository.findAllByDeletedFalseOrderByIdAsc()) {
            val id = l.id ?: continue
            val name = l.logisticName?.trim().orEmpty().ifEmpty { "—" }
            val plate = l.carPlate?.trim().orEmpty().ifEmpty { "—" }
            val created = l.created
            val modified = l.modified

            if (inOpenInterval(created)) {
                out.add(
                    LogisticMasterDiffLine(
                        logisticId = id,
                        type = "ADDED",
                        logisticName = name,
                        carPlate = plate,
                        changeText = "新增物流公司：$name（$plate）",
                    ),
                )
                continue
            }

            if (created != null && !created.isAfter(fromAt) && inOpenInterval(modified)) {
                out.add(
                    LogisticMasterDiffLine(
                        logisticId = id,
                        type = "EDITED",
                        logisticName = name,
                        carPlate = plate,
                        changeText = "修改物流公司：$name（$plate）",
                    ),
                )
            }
        }
        return out
    }

    @Transactional
    open fun updateNote(versionId: Long, note: String?): TruckLaneVersionResponse {
        val v = truckLaneVersionRepository.findByIdAndDeletedFalse(versionId)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Version not found: $versionId")
        val trimmed = note?.trim()?.takeIf { it.isNotEmpty() }
        v.note = trimmed
        return toResponse(truckLaneVersionRepository.save(v))
    }

    @Transactional
    open fun restore(versionId: Long): String {
        val version = truckLaneVersionRepository.findByIdAndDeletedFalse(versionId)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Version not found: $versionId")

        val lines = truckLaneVersionLineRepository.findAllByTruckLaneVersion_IdAndDeletedFalseOrderByIdAsc(versionId)
        if (lines.isEmpty()) return "No lines to restore for versionId=$versionId"

        val snapshottedIds = lines.mapNotNull { it.truckRowId }.filter { it > 0 }.toSet()
        if (snapshottedIds.isEmpty()) {
            return "No valid truckRowIds in snapshot for versionId=$versionId"
        }

        val fullBoard = isFullBoardSnapshot(version, lines)
        if (fullBoard) {
            val currentAll = truckRepository.findAllForRouteBoard()
            val extras = currentAll.filter { t -> t.id != null && t.id !in snapshottedIds }
            extras.forEach { it.deleted = true }
            if (extras.isNotEmpty()) {
                truckRepository.saveAll(extras)
            }
        } else {
            val lane = version.truckLanceCode!!.trim()
            if (lane.isNotEmpty()) {
                val currentLane = truckRepository.findAllByTruckLanceCodeAndDeletedFalse(lane)
                val extras = currentLane.filter { t -> t.id != null && t.id !in snapshottedIds }
                extras.forEach { it.deleted = true }
                if (extras.isNotEmpty()) {
                    truckRepository.saveAll(extras)
                }
            }
        }

        val trucksById = truckRepository.findAllById(snapshottedIds.toList()).associateBy { it.id }

        val updated = lines.mapNotNull { line ->
            val truckId = line.truckRowId ?: return@mapNotNull null
            if (truckId <= 0) return@mapNotNull null
            val truck = trucksById[truckId] ?: return@mapNotNull null

            truck.deleted = false
            truck.apply {
                // Restore only the fields we snapshot.
                this.truckLanceCode = line.truckLanceCode ?: version.truckLanceCode
                this.loadingSequence = line.loadingSequence
                this.districtReference = line.districtReference
                val sid = line.storeId?.trim()?.takeUnless { it.isEmpty() || it == "-" }
                if (sid != null) this.storeId = sid
                this.shopCode = line.shopCode
                this.shopName = line.branchName
                this.remark = line.remark
                this.departureTime =
                    line.departureTime?.trim()?.takeIf { it.isNotEmpty() }?.let { LocalTime.parse(it) }
                val lid = line.logisticId
                this.logistic =
                    if (lid != null && lid > 0) {
                        logisticRepository.findByIdAndDeletedFalse(lid)
                    } else {
                        null
                    }
            }
        }
        if (updated.isNotEmpty()) {
            truckRepository.saveAll(updated)
        }

        createSnapshot(
            CreateTruckLaneSnapshotRequest(
                truckLanceCode = null,
                note = "restore from versionId=$versionId",
            )
        )

        return "Restored versionId=$versionId"
    }
}
