package com.tencent.qian.webview.scheme

import com.tencent.qian.webview.model.SignResult
import java.net.URLDecoder

/**
 * `qianapp://` 协议解析器。
 *
 * **职责**:
 * 1. 识别 scheme 为 `qianapp` 的 URL
 * 2. host + path 拼接为 [SignResult.customPath]
 * 3. 解析 query,UTF-8 percent-decoded
 * 4. 抽取 4 个 recognized field 到对应字段(`flowId` / `action` / `result` / `from`)
 * 5. 其余 query 透传到 [SignResult.extras]
 *
 * **解析约定(与 iOS `SignResultParser.swift` 1:1 对齐)**:
 * - 重复 key 取**首个**(后续覆盖丢弃)
 * - 空 value(`?action=`)保留为空字符串而非 null
 * - 未知 action / result 值**原样透出**,不映射到 UNKNOWN
 * - 非法 URL / scheme 不匹配 → 返回 [SignResult.EMPTY]
 * - `errorCode` / `errorMsg` 等历史字段 → 落入 [SignResult.extras](保留向前兼容)
 *
 * **不依赖 Android `android.net.Uri`**:为了让单测可以在纯 JVM(JUnit + Robolectric off)下跑,
 * 用 `java.net.URI` + 手写 query parser。
 */
public object SignResultParser {

    private const val SCHEME = "qianapp"
    private const val SCHEME_PREFIX = "$SCHEME://"

    /** URL 最大长度限制(8KB),超过视为恶意构造,返回 [SignResult.EMPTY] 并丢弃。 */
    private const val MAX_URL_LENGTH = 8 * 1024

    private val RECOGNIZED_KEYS: Set<String> = setOf("flowId", "action", "result", "from")

    /**
     * 判断给定 URL 是否为本 SDK 关心的 `qianapp://` scheme。
     *
     * 注:此方法不做严格 URL 合法性校验,只看前缀。
     */
    @JvmStatic
    public fun isQianAppScheme(url: String?): Boolean {
        if (url.isNullOrEmpty()) return false
        return url.startsWith(SCHEME_PREFIX, ignoreCase = true)
    }

    /**
     * 解析 `qianapp://...` URL。
     *
     * 失败时返回 [SignResult.EMPTY] (`rawUrl` 同样为空,而非传入值,与设计要点对齐)。
     *
     * @param url 完整 URL,例 `qianapp://callback/sign?flowId=abc&action=sign&result=success&from=tencent_ess`
     */
    @JvmStatic
    public fun parse(url: String?): SignResult {
        if (url.isNullOrEmpty()) return SignResult.EMPTY
        if (!isQianAppScheme(url)) return SignResult.EMPTY
        // 防御超长 URL(可能导致 OOM):超过 8KB 直接丢弃
        if (url.length > MAX_URL_LENGTH) return SignResult.EMPTY

        // 拆 scheme://body[?query]
        val body = url.substring(SCHEME_PREFIX.length)
        val (pathPart, queryPart) = splitOnce(body, '?')

        // host + path 即为 customPath
        // 例:body = "callback/sign" → customPath = "callback/sign"
        // 例:body = "result"        → customPath = "result"
        val customPath = pathPart.trim('/')

        // 解析 query
        val parsed = parseQuery(queryPart)

        return SignResult(
            rawUrl = url,
            customPath = customPath,
            flowId = parsed["flowId"].orEmpty(),
            action = parsed["action"].orEmpty(),
            result = parsed["result"].orEmpty(),
            from = parsed["from"].orEmpty(),
            extras = parsed.filterKeys { it !in RECOGNIZED_KEYS }
        )
    }

    /** 在第一个 [delimiter] 上拆分,返回 (左, 右)。无分隔符时右半为空字符串。 */
    private fun splitOnce(input: String, delimiter: Char): Pair<String, String> {
        val idx = input.indexOf(delimiter)
        return if (idx < 0) input to "" else input.substring(0, idx) to input.substring(idx + 1)
    }

    /**
     * 解析 query 字符串。
     *
     * 规则:
     * - 以 `&` 分割 pair
     * - 每个 pair 以**第一个** `=` 拆 key / value
     * - key 与 value 都做 UTF-8 percent-decoded
     * - 空 key 跳过;空 value 保留为空字符串
     * - **重复 key 保留首个**
     * - decode 失败的 pair 跳过(防御非法 URL)
     */
    private fun parseQuery(query: String): LinkedHashMap<String, String> {
        val out = LinkedHashMap<String, String>()
        if (query.isEmpty()) return out

        for (pair in query.split('&')) {
            if (pair.isEmpty()) continue
            val (rawKey, rawValue) = splitOnce(pair, '=')
            val key = decodeOrNull(rawKey) ?: continue
            if (key.isEmpty()) continue
            // 重复 key 保留首个
            if (out.containsKey(key)) continue
            val value = decodeOrNull(rawValue) ?: ""
            out[key] = value
        }
        return out
    }

    private fun decodeOrNull(raw: String): String? = try {
        // URLDecoder 把 '+' 转成空格,符合 application/x-www-form-urlencoded;
        // qianapp 协议 query 也走这个语义,与 H5 团队约定一致。
        URLDecoder.decode(raw, "UTF-8")
    } catch (_: IllegalArgumentException) {
        null
    } catch (_: Exception) {
        null
    }
}
