/*
 * Copyright (c) 2025 SimcuTeam. All rights reserved.
 * 遵循 MIT 许可证。
 */

package simcu::simapi.middlewares

import std.collection.*
import std.io.*
import std.time.*
import soulsoft_web_http.*
import soulsoft_extensions_logging.*
import simcu::serialization.*
import simcu::simapi.communications.*
import simcu::simapi.configurations.*
import simcu::simapi.helpers.*

/**
 * 请求日志中间件：记录请求方法、URL、请求头、请求体、响应状态码、响应体、耗时与异常。
 *
 * 对齐说明：
 *   - 请求体按 JSON 字段级截断
 *   - 捕获下游异常并记录，随后重抛
 *   - 响应体：soulsoft HttpResponse.body 只读且不可读回（read 抛 UnsupportedException），
 *     无法像原版那样用 MemoryStream 替换捕获；改为在各统一写出入口
 *     （SimApiResponseWriter）缓存响应文本，此处直接读取。
 *     响应行格式：*( Response [status] ) => [ N bytes ]，随后换行输出响应体结构；
 *     ShowFullResponse=false 时截断到 200 字符
 *   - ShowFullUrl=false 时仅显示路径+查询串；ShowRunTime=true 时请求行显示 [POST] (xxxms)
 */
public class SimApiRequestLogMiddleware <: IMiddleware {
    private let _options: SimApiOptions
    private let _logger: ILogger

    public init(options: SimApiOptions, loggerFactory: ILoggerFactory) {
        _options = options
        _logger = loggerFactory.createLogger("SimApi.RequestLog")
    }

    /**
     * 中间件入口（IMiddleware 风格）。
     */
    public func invoke(context: HttpContext, next: RequestDelegate): Unit {
        let start = MonoTime.now()
        let options = _options.simApiRequestLogOptions
        var sb = StringBuilder()

        // 请求头
        if (options.showFullHeader) {
            sb.append("*( RequestHeaders [Full] ) =>\n")
            sb.append(serializeHeaders(context))
        } else {
            sb.append("*( RequestHeaders ) =>\n")
            let token = context.request.headers.get("Token") ?? ""
            let queryId = context.request.headers.get("Query-Id") ?? ""
            sb.append("Token: ${token}  QueryId: ${queryId}\n")
        }

        // 请求体
        sb.append("*( RequestBody ) =>\n")
        sb.append(readRequestBody(context))

        // 调用下一级，捕获异常以便记录并重抛
        var exception: ?Exception = None
        try {
            next(context)
        } catch (ex: Exception) {
            exception = Some(ex)
        }

        // 请求行（耗时需在 next 之后计算，故最后拼装）
        let elapsed = MonoTime.now() - start
        let elapsedMs = elapsed / Duration.millisecond
        let url = if (options.showFullUrl) {
            buildDisplayUrl(context)
        } else {
            context.request.path.toString() + context.request.queryString.toString()
        }
        var sbHead = StringBuilder()
        sbHead.append("[${context.request.method}]")
        if (options.showRunTime) {
            sbHead.append(" (${elapsedMs}ms)")
        }
        sbHead.append(" ${url}\n")

        // 响应信息：状态码 + 响应体长度（bytes），响应体结构换行显示
        let responseBody = readResponseBody(context)
        let bodyLen = if (responseBody.isEmpty()) {
            context.response.contentLength ?? 0
        } else {
            responseBody.size
        }
        sb.append("*( Response [${context.response.statusCode}] ) => [ ${bodyLen} bytes ]\n")
        if (!responseBody.isEmpty()) {
            let display = if (_options.simApiRequestLogOptions.showFullResponse) {
                responseBody
            } else if (responseBody.size > 200) {
            // ShowFullResponse=false 时截断到 200 字符（长度仍显示完整字节数）
                responseBody[0..200] + "...(${responseBody.size})"
            } else {
                responseBody
            }
            // 去掉响应体末尾换行：结构体最后不多加换行（logger 收尾会补一个换行）
            var text = display
            while (text.endsWith("\n")) {
                text = text[0..text.size - 1]
            }
            sb.append(text)
        }
        if (let Some(ex) <- exception) {
            sb.append("\nException: ${ex.toString()}\n")
        }

        _logger.info(sbHead.toString() + sb.toString())

        // 重抛原异常，由外层 ExceptionMiddleware 处理
        if (let Some(ex) <- exception) {
            throw ex
        }
    }

    /// 构造完整请求 URL：stdx 服务端请求 URL 只有路径（无 scheme/host），需手动拼接。
    /// {Scheme}://{Host}{Path}{QueryString}；Host 优先取 Host 请求头。
    private func buildDisplayUrl(context: HttpContext): String {
        var sb = StringBuilder()
        let scheme = context.request.scheme
        sb.append(if (scheme.isEmpty()) { "http" } else { scheme })
        sb.append("://")
        let hostHeader = context.request.headers.get("Host") ?? ""
        if (!hostHeader.isEmpty()) {
            sb.append(hostHeader)
        } else {
            let host = context.request.host.toString()
            if (!host.isEmpty()) {
                sb.append(host)
            }
        }
        sb.append(context.request.path.toString())
        sb.append(context.request.queryString.toString())
        sb.toString()
    }

    private func serializeHeaders(context: HttpContext): String {
        var sb = StringBuilder()
        sb.append("{")
        var first = true
        for ((name, values) in context.request.headers) {
            if (!first) { sb.append(",") }
            sb.append("\"${SimApiUtil.escapeJson(name)}\":\"${SimApiUtil.escapeJson(joinValues(values))}\"")
            first = false
        }
        sb.append("}\n")
        sb.toString()
    }

    private func joinValues(values: Collection<String>): String {
        var sb = StringBuilder()
        var first = true
        for (v in values) {
            if (!first) { sb.append(",") }
            sb.append(v)
            first = false
        }
        sb.toString()
    }

    private func readRequestBody(context: HttpContext): String {
        try {
            context.request.enableBuffering()
            var buffer = Array<Byte>(4096, repeat: 0)
            var sb = StringBuilder()
            var read = context.request.body.read(buffer)
            while (read > 0) {
                sb.appendFromUtf8(buffer.slice(0, read))
                read = context.request.body.read(buffer)
            }
            let bodyText = sb.toString()
            // 重置流位置，供后续业务读取；同时缓存 body（流可能不可重读）
            if (let seekable: Seekable <- context.request.body) {
                seekable.seek(SeekPosition.Begin(0))
            }
            context.items["SimApi:BodyCache"] = bodyText
            return truncateBody(bodyText)
        } catch (_: Exception) {
            return "(读取请求体失败)\n"
        }
    }

    /// 响应体读取：从 SimApiResponseWriter 缓存取完整响应体；未捕获到时返回空串
    /// （长度与截断由调用方处理）
    private func readResponseBody(context: HttpContext): String {
        match (context.items.get(SimApiResponseWriter.responseBodyCacheKey)) {
            case Some(v) => if (let s: String <- v) { s } else { "" }
            case None => ""
        }
    }

    /// 请求体截断：JSON 字段级截断，非 JSON 则整串截断
    private func truncateBody(body: String): String {
        let maxLen = _options.simApiRequestLogOptions.requestStringLogLength
        if (maxLen <= 0) {
            return body + "\n"
        }
        try {
            // 用 simapi_serialization 解析为动态结构，仅截断超长字符串字段（保持结构）
            let map = JsonSerializer.deserialize<HashMap<String, Any>>(body)
            var sb = StringBuilder()
            sb.append("{")
            var first = true
            for ((k, v) in map) {
                if (!first) { sb.append(",") }
                sb.append("\"${SimApiUtil.escapeJson(k)}\":")
                if (let s: String <- v) {
                    if (s.size > maxLen) {
                        sb.append("\"${SimApiUtil.escapeJson(s[0..maxLen])}...(${s.size})\"")
                    } else {
                        sb.append("\"${SimApiUtil.escapeJson(s)}\"")
                    }
                } else {
                    sb.append(SimApiUtil.json(Some(v)))
                }
                first = false
            }
            sb.append("}")
            return sb.toString() + "\n"
        } catch (_: Exception) {
        }
        // 非 JSON 或解析失败：整串按长度截断
        if (body.size <= maxLen) {
            return body + "\n"
        }
        return body[0..maxLen] + "...(${body.size})\n"
    }
}
