package com.ffii.fpsms.modules.report.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.ffii.fpsms.m18.entity.M18BomShopSyncLog
import com.ffii.fpsms.m18.entity.M18BomShopSyncLogRepository
import com.ffii.fpsms.m18.model.GoodsReceiptNoteResponse
import com.ffii.fpsms.m18.model.M18BomForShopSaveRequest
import com.ffii.fpsms.m18.model.M18UdfProductSaveValue
import com.ffii.fpsms.modules.master.entity.BomRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Service
open class M18BomShopSyncReportService(
    private val m18BomShopSyncLogRepository: M18BomShopSyncLogRepository,
    private val bomRepository: BomRepository,
    private val itemsRepository: ItemsRepository,
    private val objectMapper: ObjectMapper,
) {
    private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
    private val versionFromHeaderCode = Regex("V(\\d+)$", RegexOption.IGNORE_CASE)

    open fun searchBomShopSyncHistoryReport(
        syncDateStart: String?,
        syncDateEnd: String?,
        finishedItemCode: String?,
        syncStatus: String?,
    ): Map<String, Any> {
        val start = parseDateStart(syncDateStart)
        val end = parseDateEnd(syncDateEnd)
        val itemFilter = finishedItemCode?.trim()?.takeIf { it.isNotEmpty() }
        val statusFilter = syncStatus?.trim()?.lowercase()?.takeIf { it.isNotEmpty() && it != "all" }

        val logs = m18BomShopSyncLogRepository.searchForReport(
            syncDateStart = start,
            syncDateEnd = end,
            finishedItemCode = itemFilter,
            syncStatus = statusFilter,
        )

        val syncRows = mutableListOf<Map<String, Any?>>()
        val materialRows = mutableListOf<Map<String, Any?>>()
        var successCount = 0
        var failedCount = 0
        var skippedUnchangedCount = 0

        for (log in logs) {
            val bomId = log.bomId ?: continue
            val bom = bomRepository.findByIdAndDeletedIsFalse(bomId)
            val finishedCode = log.finishedItemCode?.trim().orEmpty()
            val itemName =
                finishedCode.takeIf { it.isNotEmpty() }
                    ?.let { itemsRepository.findByCodeAndDeletedFalse(it)?.name?.trim() }
                    ?: bom?.name?.trim()?.takeIf { it.isNotEmpty() }
            val headerCode = log.m18HeaderCode?.trim().orEmpty()
            val version = extractVersion(headerCode)
            val status = resolveSyncStatus(log)
            when (status) {
                "SUCCESS" -> successCount++
                "SKIPPED_UNCHANGED" -> skippedUnchangedCount++
                else -> failedCount++
            }
            val syncAt = log.created?.format(dateTimeFormatter)
            val failureReason = resolveFailureReason(log).takeIf { status == "FAILED" }

            syncRows.add(
                linkedMapOf(
                    "syncLogId" to log.id,
                    "syncDateTime" to syncAt,
                    "bomId" to bomId,
                    "bomRoutingCode" to bom?.code,
                    "finishedItemCode" to finishedCode.ifEmpty { null },
                    "finishedItemName" to itemName,
                    "m18HeaderCode" to headerCode.ifEmpty { null },
                    "version" to version,
                    "m18RecordId" to log.m18RecordId?.takeIf { it > 0L },
                    "syncStatus" to status,
                    "synced" to log.synced,
                    "m18ApiStatus" to log.m18ApiStatus,
                    "failureReason" to failureReason,
                    "message" to log.message,
                ),
            )

            val materials = parseMaterialLines(log)
            for (line in materials) {
                materialRows.add(
                    linkedMapOf(
                        "syncLogId" to log.id,
                        "syncDateTime" to syncAt,
                        "bomId" to bomId,
                        "finishedItemCode" to finishedCode.ifEmpty { null },
                        "m18HeaderCode" to headerCode.ifEmpty { null },
                        "version" to version,
                        "syncStatus" to status,
                        "lineNo" to line.itemNo?.trim(),
                        "materialName" to line.udfIngredients?.trim(),
                        "udfProductM18Id" to line.udfProduct,
                        "udfBaseUnit" to line.udfBaseUnit,
                        "udfQty" to line.udfqty,
                        "udfSupplierM18Id" to line.udfSupplier,
                        "udfPurchaseUnitM18Id" to line.udfpurchaseUnit,
                    ),
                )
            }
        }

        return mapOf(
            "summary" to mapOf(
                "totalAttempts" to logs.size,
                "success" to successCount,
                "skippedUnchanged" to skippedUnchangedCount,
                "failed" to failedCount,
                "syncDateStart" to syncDateStart,
                "syncDateEnd" to syncDateEnd,
            ),
            "syncRows" to syncRows,
            "materialRows" to materialRows,
        )
    }

    private fun parseDateStart(raw: String?): LocalDateTime? {
        if (raw.isNullOrBlank()) return null
        return LocalDate.parse(raw.trim().replace("/", "-")).atStartOfDay()
    }

    private fun parseDateEnd(raw: String?): LocalDateTime? {
        if (raw.isNullOrBlank()) return null
        return LocalDate.parse(raw.trim().replace("/", "-")).atTime(23, 59, 59, 999_999_999)
    }

    private fun extractVersion(headerCode: String?): String? {
        if (headerCode.isNullOrBlank()) return null
        return versionFromHeaderCode.find(headerCode.trim())?.groupValues?.get(1)
    }

    private fun resolveSyncStatus(log: M18BomShopSyncLog): String = when {
        !log.synced -> "FAILED"
        log.message?.contains("skippedUnchanged", ignoreCase = true) == true -> "SKIPPED_UNCHANGED"
        else -> "SUCCESS"
    }

    private fun resolveFailureReason(log: M18BomShopSyncLog): String? {
        log.message?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
        val json = log.responseJson?.trim().orEmpty().ifEmpty { return null }
        return runCatching {
            val resp = objectMapper.readValue(json, GoodsReceiptNoteResponse::class.java)
            resp.messages
                .joinToString("; ") { m -> m.msgDetail?.trim().orEmpty().ifEmpty { m.msgCode.orEmpty() } }
                .trim()
                .ifEmpty { null }
        }.getOrNull()
    }

    private fun parseMaterialLines(log: M18BomShopSyncLog): List<M18UdfProductSaveValue> {
        val json = log.requestJson?.trim().orEmpty().ifEmpty { return emptyList() }
        return runCatching {
            objectMapper.readValue(json, M18BomForShopSaveRequest::class.java)
                .udfproduct.values
        }.getOrElse { emptyList() }
    }
}
