/*
 * Copyright (c) 2025 SimcuTeam. All rights reserved.
 * 遵循 MIT 许可证。
 * AuthSDK/SimApiAuthCenter：认证中心远程 SDK。
 */

package simcu::simapi.authsdk

import std.collection.*
import std.io.*
import stdx.net.http.*
import stdx.net.tls.*
import stdx.net.tls.common.*
import simcu::serialization.*
import simcu::simapi.communications.*
import simcu::simapi.helpers.*

/**
 * 认证中心远程 SDK：
 * 群组 / Profile / 内部应用 / 系统登录 / 安全验证 等接口，走签名请求。
 */
public class SimApiAuthCenter {
    private let _client: SimApiAuthClient

    public init(client: SimApiAuthClient) {
        this._client = client
    }

    public prop client: SimApiAuthClient {
        get() {
            _client
        }
    }

    // ===== 公共 =====

    /**
     * 委托 AuthCenter 进行应用签名验证。
     */
    public func verifySign(appId: String, timestamp: String, nonce: String, sign: String): Unit {
        let url = "${_client.server}/api/auth/sign/verify?appId=${appId}&timestamp=${timestamp}&nonce=${nonce}&sign=${sign}"
        let http = ClientBuilder().
            noProxy().
            tlsConfig(buildTlsConfig(_client.server)).
            build()
        try {
            let request = HttpRequestBuilder().
                post().
                url(url).
                header("Content-Type", "application/json").
                body("{}").
                build()
            let response = http.send(request)
            try {
                SimApiError.errorWhenFalse(isSuccess(response.status), code: Int64(response.status),
                    message: "HTTP ERROR: ${response.status}")
                let json = readBodyText(response.body)
                let resp = JsonSerializer.deserialize<SimApiBaseResponse>(json)
                SimApiError.errorWhen(resp.code != 200, code: 400, message: "签名验证失败")
            } finally {
                response.close()
            }
        } finally {
            http.close()
        }
    }

    /// 构建 TLS 配置：信任所有证书 + SNI 域名
    private static func buildTlsConfig(server: String): TlsClientConfig {
        var tls = TlsClientConfig()
        tls.verifyMode = CertificateVerifyMode.TrustAll
        match (server.indexOf("://")) {
            case Some(i) =>
                let rest = server[i + 3..]
                let slash = rest.indexOf("/") ?? rest.size
                let q = rest.indexOf("?") ?? rest.size
                let end = if (slash < q) { slash } else { q }
                let host = rest[0..end]
                if (!host.isEmpty()) {
                    tls.serverName = Some(host)
                }
            case None => ()
        }
        tls
    }

    /// 2xx 视为成功
    private static func isSuccess(status: UInt16): Bool {
        status >= 200 && status < 300
    }

    /// 读取响应体 InputStream 为字符串
    private static func readBodyText(body: InputStream): String {
        var buffer = Array<Byte>(4096, repeat: 0)
        var sb = StringBuilder()
        var read = body.read(buffer)
        while (read > 0) {
            sb.appendFromUtf8(buffer.slice(0, read))
            read = body.read(buffer)
        }
        sb.toString()
    }

    // ===== 群组相关 =====

    /**
     * 根据 profileId 获取群组列表。
     */
    public func groupRelated(profileId: String): Array<GroupRelatedItem> {
        _client.signQuery<Array<GroupRelatedItem>>("/api/auth/group/related",
            body: simpleBody("profileId", profileId))
    }

    /**
     * 按关键字搜索群组，输入群组 ID 精准搜索。
     */
    public func groupSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> {
        var body = HashMap<String, Any>()
        body["keyword"] = keyword
        body["skip"] = skip
        body["take"] = take
        _client.signQuery<Array<AppAndProfileItem>>("/api/auth/group/search", body: SimApiUtil.json(Some(body)))
    }

    /**
     * 使用组 ID 以及组内成员/管理员 profile 获取组的详细树结构。
     */
    public func groupDetail(groupId: String, profileId: String): GroupDetailTreeNode {
        var body = HashMap<String, Any>()
        body["profileId"] = profileId
        body["groupId"] = groupId
        _client.signQuery<GroupDetailTreeNode>("/api/auth/group/detail", body: SimApiUtil.json(Some(body)))
    }

    /**
     * 获取 profile 在本组的所有子组。
     */
    public func groupRelatedIndex(groupId: String, profileId: String): Array<String> {
        var body = HashMap<String, Any>()
        body["groupId"] = groupId
        body["profileId"] = profileId
        _client.signQuery<Array<String>>("/api/auth/internal/group/related-group-ids",
            body: SimApiUtil.json(Some(body)))
    }

    // ===== Profile 相关 =====

    /**
     * 按关键字搜索用户 Profile。
     */
    public func profileSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> {
        var body = HashMap<String, Any>()
        body["keyword"] = keyword
        body["skip"] = skip
        body["take"] = take
        _client.signQuery<Array<AppAndProfileItem>>("/api/auth/profile/search", body: SimApiUtil.json(Some(body)))
    }

    /**
     * 通过 id 批量获取用户基本信息。
     */
    public func profileList(ids: Array<String>): Array<AppAndProfileItem> {
        var body = HashMap<String, Any>()
        var arr = ArrayList<Any>()
        for (id in ids) {
            arr.add(id)
        }
        body["ids"] = arr.toArray()
        _client.signQuery<Array<AppAndProfileItem>>("/api/auth/profile/list", body: SimApiUtil.json(Some(body)))
    }

    // ===== AuthGate 内部应用专用 =====

    /**
     * 获取是否为 App 的拥有者。
     */
    public func checkIsAppOwner(profileId: String, applicationId: String): Bool {
        var body = HashMap<String, Any>()
        body["ProfileId"] = profileId
        body["AppId"] = applicationId
        _client.signQuery<Bool>("/api/auth/internal/app/check-owner", body: SimApiUtil.json(Some(body)))
    }

    /**
     * 根据用户 profileId 和提供的 appIds 获取应用列表。
     */
    public func getAppList(profileId: String, appIds: Array<String>): Array<AppAndProfileItem> {
        var body = HashMap<String, Any>()
        body["ProfileId"] = profileId
        var arr = ArrayList<Any>()
        for (id in appIds) {
            arr.add(id)
        }
        body["AllowedAppIds"] = arr.toArray()
        _client.signQuery<Array<AppAndProfileItem>>("/api/auth/internal/app/related",
            body: SimApiUtil.json(Some(body)))
    }

    // ===== 系统登录 =====

    /**
     * 获取登录授权 CODE。
     * @param scene 场景标识。
     * @param data 附加数据。
     * @param backUrl 回调地址。
     * @return GetCodeResponse（含 Code/Server/FullUrl）。
     */
    public func getLoginCode(scene!: ?String = None, data!: ?HashMap<String, Any> = None,
        backUrl!: ?String = None): GetCodeResponse {
        var body = HashMap<String, Any>()
        if (let Some(scene) <- scene) { body["scene"] = scene }
        if (let Some(data) <- data) { body["data"] = data }
        if (let Some(backUrl) <- backUrl) { body["backUrl"] = backUrl }
        let code = _client.signQuery<String>("/api/auth/login/code", body: SimApiUtil.json(Some(body)))
        let server = _client.server
        GetCodeResponse(code, server, "${server}/auth?code=${code}")
    }

    /**
     * 使用 code 获取登录信息。
     */
    public func getLoginInfo(code: String, scene!: ?String = None): LoginInfoResponse {
        var body = HashMap<String, Any>()
        body["code"] = code
        // 说明：ErrorWhenNull(resp, 400232, "登录信息获取失败") 对应 signQuery 内部 data.getOrThrow() 的
        // None 分支；仓颉版 signQuery 返回非空 T（data 缺失即抛异常），故此处无需重复判空。
        let resp = _client.signQuery<LoginInfoResponse>("/api/auth/login/get", body: SimApiUtil.json(Some(body)))
        SimApiError.errorWhen(resp.scene != scene, code: 403003, message: "登录场景不匹配")
        resp
    }

    // ===== 安全验证 =====

    /**
     * 获取安全验证代码。
     */
    public func getConfirmCode(scene: String, userId: String, data!: ?HashMap<String, Any> = None,
        backUrl!: ?String = None): GetCodeResponse {
        var body = HashMap<String, Any>()
        body["scene"] = scene
        if (let Some(data) <- data) { body["data"] = data }
        if (let Some(backUrl) <- backUrl) { body["backUrl"] = backUrl }
        body["profileId"] = userId
        let code = _client.signQuery<String>("/api/auth/confirm/code", body: SimApiUtil.json(Some(body)))
        let server = _client.server
        GetCodeResponse(code, server, "${server}/confirm?code=${code}")
    }

    /**
     * 使用安全验证 code 获取验证结果。
     */
    public func confirm(code: String, scene: String, userId!: ?String = None): ConfirmResponse {
        var body = HashMap<String, Any>()
        body["code"] = code
        let resp = _client.signQuery<ConfirmResponse>("/api/auth/confirm/get", body: SimApiUtil.json(Some(body)))
        SimApiError.errorWhen(userId != Some(resp.profileId), code: 403002, message: "安全确认身份不匹配")
        SimApiError.errorWhen(resp.scene != scene, code: 403003, message: "安全确认场景不匹配")
        resp
    }

    /// 简单单字段请求体：{"field":"value"}
    private static func simpleBody(field: String, value: String): String {
        "{\"${field}\":\"${SimApiUtil.escapeJson(value)}\"}"
    }
}
