package com.ffii.fpsms.m18.service

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.ffii.fpsms.api.service.ApiCallerService
import com.ffii.fpsms.m18.M18Config
import com.ffii.fpsms.m18.model.GoodsReceiptNoteRequest
import com.ffii.fpsms.m18.model.GoodsReceiptNoteResponse
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.util.LinkedMultiValueMap
import reactor.core.publisher.Mono

/**
 * Service to create Goods Receipt Note (AN) in M18 via the save API.
 * API: PUT http://[server]/jsf/rfws/root/api/save/an
 * Query: menuCode=an (required), param (optional, JSON string)
 * Headers: authorization (Bearer token), client_id
 */
@Service
open class M18GoodsReceiptNoteService(
    private val m18Config: M18Config,
    private val apiCallerService: ApiCallerService,
) {
    private val logger: Logger = LoggerFactory.getLogger(M18GoodsReceiptNoteService::class.java)

    private val M18_SAVE_GOODS_RECEIPT_NOTE_API = "/root/api/save/an"
    private val M18_READ_GOODS_RECEIPT_NOTE_API = "/root/api/read/an"
    private val MENU_CODE_AN = "an"

    // Serialize with UTF-8 characters (no \\uXXXX) so M18 persists bDesc/bDesc_en like Postman
    @Suppress("DEPRECATION")
    private val grnRequestMapper: ObjectMapper = jacksonObjectMapper().apply {
        disable(JsonGenerator.Feature.ESCAPE_NON_ASCII)
    }

    /**
     * Creates a goods receipt note in M18.
     *
     * @param request The request body containing mainan (header) and ant (lines).
     * @param param Optional extra parameters in JSON format (query param "param").
     * @return The M18 response with recordId, messages, and status; or null on failure.
     */
    open fun createGoodsReceiptNote(
        request: GoodsReceiptNoteRequest,
        param: String? = null,
    ): GoodsReceiptNoteResponse? {
        return createGoodsReceiptNoteMono(request, param).block()
    }

    /**
     * Creates a goods receipt note in M18 (reactive).
     *
     * @param request The request body containing mainan (header) and ant (lines).
     * @param param Optional extra parameters in JSON format (query param "param").
     * @return Mono of the M18 response.
     */
    open fun createGoodsReceiptNoteMono(
        request: GoodsReceiptNoteRequest,
        param: String? = null,
    ): Mono<GoodsReceiptNoteResponse> {
        val queryParams = LinkedMultiValueMap<String, String>().apply {
            add("menuCode", MENU_CODE_AN)
            param?.let { add("param", it) }
        }
        val queryString = queryParams.entries.flatMap { (k, v) -> v.map { value -> "$k=$value" } }.joinToString("&")
        val fullUrl = "${m18Config.BASE_URL}$M18_SAVE_GOODS_RECEIPT_NOTE_API?$queryString"
        val requestJson = grnRequestMapper.writeValueAsString(request)
        logger.info("[M18 GRN API] call=PUT url=$fullUrl body=$requestJson")
        return apiCallerService.putWithJsonString<GoodsReceiptNoteResponse>(
            urlPath = M18_SAVE_GOODS_RECEIPT_NOTE_API,
            queryParams = queryParams,
            bodyJson = requestJson,
        ).doOnSuccess { response ->
            val responseJson = grnRequestMapper.writeValueAsString(response)
            logger.info("[M18 GRN API] response (all): $responseJson")
            if (response.status) {
                logger.info("Goods receipt note created in M18. recordId=${response.recordId}")
            } else {
                logger.warn("M18 save AN returned status=false. recordId=${response.recordId}, messages=${response.messages}")
            }
        }.doOnError { e ->
            logger.error("[M18 GRN API] call failed: $fullUrl error=${e.message}", e)
        }
    }

    /**
     * Reads AN (GRN) from M18 by record id. GET /root/api/read/an?id={recordId}
     * Document code is typically at data.mainan.values[0].code or data.mainan.code.
     */
    open fun readAnCodeByRecordId(recordId: Long): String? {
        return try {
            val node: JsonNode = apiCallerService.get<JsonNode>(
                M18_READ_GOODS_RECEIPT_NOTE_API,
                mapOf("id" to recordId),
            ).block() ?: return null
            extractAnCodeFromReadResponse(node)
        } catch (e: Exception) {
            logger.warn("[M18 read AN] id=$recordId failed: ${e.message}", e)
            null
        }
    }

    private fun extractAnCodeFromReadResponse(node: JsonNode): String? {
        val paths = listOf(
            "/data/mainan/0/code",
            "/data/mainan/values/0/code",
            "/data/mainan/code",
            "/data/ant/0/code",
            "/mainan/values/0/code",
            "/mainan/code",
            "/mainan/0/code",
        )
        for (p in paths) {
            val v = node.at(p)
            if (!v.isMissingNode && !v.isNull) {
                val text = v.asText()
                if (!text.isNullOrBlank()) return text
            }
        }
        return null
    }
}
