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

package simcu::simapi.helpers

import std.convert.*
import soulsoft_web_http.*
import simcu::simapi.exceptions.*
import simcu::simapi.interfaces.*

/**
 * 服务端验签校验器。
 *
 * 仓颉无声明式 ActionFilter 机制，按项目惯例（同 requireLogin）由控制器在需要验签的方法开头调用：
 *   SimApiSignChecker.verify(context, provider, cache)
 * 或标注 @SimApiSign 注解自动执行（SimApiRequestDelegateFactory）。
 *
 * 校验流程：
 *   1. 提取 appId（Query/Header）
 *   2. provider.getKey(appId) 获取密钥
 *   3. 提取并解析 timestamp / nonce
 *   4. QueryExpires 过期校验（ts > now+2 → 校准时间；ts+expires < now → 已过期）
 *   5. DuplicateRequestProtection：nonce 去重（缓存 "SignQuery:{nonce}"）
 *   6. 拼接 SignFields + appId + timestamp + nonce + key，MD5 比对 sign
 */
public class SimApiSignChecker {
    private init() {}

    /**
     * 校验当前请求签名。
     * @param context 当前请求上下文。
     * @param provider 签名提供器（含字段名/过期/去重配置与密钥获取）。
     * @param cache 缓存（nonce 去重用；None 时跳过去重，保持兼容）。
     */
    public static func verify(context: HttpContext, provider: SimApiSignProviderBase, cache: ?SimApiCache): Unit {
        // 1. 提取 appId
        var appId: ?String = None
        if (let Some(name) <- provider.appIdName) {
            if (!name.isEmpty()) {
                appId = getParam(context, name)
                if (appId == None || appId == Some("")) {
                    SimApiError.error(code: 400, message: "获取${name}失败")
                }
            }
        }

        // 2. 获取密钥
        let key = provider.getKey(appId)
        if (key == None || key == Some("")) {
            SimApiError.error(code: 400, message: "获取签名KEY失败")
        }
        let secret = key.getOrThrow()

        // 3. 提取 timestamp / nonce
        let timestamp = getParam(context, provider.timestampName)
        if (timestamp == None || timestamp == Some("")) {
            SimApiError.error(code: 400, message: "${provider.timestampName}不能为空")
        }
        let nonce = getParam(context, provider.nonceName)
        if (nonce == None || nonce == Some("")) {
            SimApiError.error(code: 400, message: "${provider.nonceName}不能为空")
        }
        let tsStr = timestamp.getOrThrow()
        let nonceStr = nonce.getOrThrow()
        let ts = parseTimestamp(tsStr)

        // 4. 过期校验
        if (provider.queryExpires != 0) {
            let now = Int64(SimApiUtil.timestampNow)
            if (ts > now + 2) {
                SimApiError.error(code: 400, message: "请校准本地时间")
            }
            if (ts + provider.queryExpires < now) {
                SimApiError.error(code: 400, message: "请求已过期")
            }

            // 5. nonce 去重
            if (provider.duplicateRequestProtection) {
                if (let Some(cache) <- cache) {
                    let nonceKey = "SignQuery:${nonceStr}"
                    if (!cache.hasKey(nonceKey)) {
                        cache.set(nonceKey, tsStr, expireSeconds: provider.queryExpires + 2)
                    } else {
                        SimApiError.error(code: 400, message: "重复请求")
                    }
                }
            }
        }

        // 6. 拼接签名串并比对
        var sb = StringBuilder()
        for (field in provider.signFields) {
            sb.append("${field}=")
            if (let Some(v) <- getParam(context, field)) {
                sb.append(v)
            }
            sb.append("&")
        }
        if (let Some(name) <- provider.appIdName) {
            if (!name.isEmpty()) {
                sb.append("${name}=${appId.getOrThrow()}&")
            }
        }
        sb.append("${provider.timestampName}=${ts}&${provider.nonceName}=${nonceStr}&${secret}")
        let expect = SimApiUtil.md5(sb.toString())
        let sign = getParam(context, provider.signName)
        if (sign == None || sign != Some(expect)) {
            SimApiError.error(code: 400, message: "签名错误")
        }
    }

    /// 从 Query 或 Header 取参数（Query 优先）
    private static func getParam(context: HttpContext, name: String): ?String {
        let q = context.request.query.get(name)
        if (q != None && q != Some("")) {
            return q
        }
        context.request.headers.get(name)
    }

    /// 解析秒级时间戳
    private static func parseTimestamp(s: String): Int64 {
        try {
            Int64.parse(s)
        } catch (ex: Exception) {
            SimApiError.error(code: 400, message: "时间戳格式错误")
        }
        Int64.parse(s)
    }
}
