//
//  WeChatAuth.swift
//  ylgamehall
//
//  微信 OAuth2 客户端直拼实现（与 msext 4 处 ViewController 同款路径）。
//  Code → access_token → userinfo → 7 字段 user。
//
//  ⚠️ AppSecret 客户端持有 = 父项目 CLAUDE.md 已识别但接受的安全风险。
//  msext IPA 已分发数年，Appsecret 等价泄露；沿用同一 secret 不引入新攻击面，
//  也无需后台 /wechat/login 中转。详见 docs/SDK-Integration-Guide.md §0.1。
//
//  存储位置：AppSecrets.plist 的 wxAppSecret 字段（唯一来源），由
//  AppSecrets.shared 启动期一次性加载。
//
//  字段名严格 1:1（与 msext NewRootVC.m:2428 sharelogin payload 等价）：
//    openid / headimgurl / nickname / sex / city / Province（大写 P）/ unionid
//

import Foundation

public struct WeChatUser: Sendable {
    public let openid:     String
    public let headimgurl: String
    public let nickname:   String
    /// 性别。微信回的是 number（1=男 2=女 0=未知）；H5 收到的 sharelogin 是同款 number 字面。
    public let sex:        Int
    /// city / province 经 msext `danbian:` 去单引号处理（防 H5 JSON 解析炸）
    public let city:       String
    /// 注意大写 P：msext NewRootVC.m:2428 payload key 是 "Province"，硬约束
    public let Province:   String
    public let unionid:    String
}

public enum WeChatAuthError: Error, Sendable {
    case userCancelled
    case authStepFailed(Int)
    case tokenRequestFailed(any Error)
    case tokenResponseInvalid(String)
    case userInfoRequestFailed(any Error)
    case userInfoResponseInvalid(String)
}

@MainActor
public enum WeChatAuth {

    /// 微信 AppSecret — 来源 `AppSecrets.plist` 的 `wxAppSecret`（启动期加载）
    static var appSecret: String { AppSecrets.shared.wxAppSecret }

    /// 完整 OAuth2 流程：拉起授权 → 拿 code → 换 access_token → 拿 userinfo → 返回 7 字段 user
    public static func authorize() async throws -> WeChatUser {
        // Step 1: 拿 code
        let payload: WXAuthCodePayload
        do {
            payload = try await WeChatManager.shared.authorize()
        } catch let WeChatError.authFailed(errCode) {
            // msext 行为：errCode == -2 视为用户取消，不弹错误
            if errCode == -2 { throw WeChatAuthError.userCancelled }
            throw WeChatAuthError.authStepFailed(errCode)
        }
        // Step 2: code → access_token + openid
        let token = try await exchangeAccessToken(code: payload.code)
        // Step 3: access_token + openid → userinfo（7 字段）
        return try await fetchUserInfo(accessToken: token.accessToken, openid: token.openid)
    }

    // MARK: - Private — OAuth2 step 2/3 directly hitting api.weixin.qq.com

    private struct TokenResponse {
        let accessToken: String
        let openid:      String
    }

    private static func exchangeAccessToken(code: String) async throws -> TokenResponse {
        let urlString = "https://api.weixin.qq.com/sns/oauth2/access_token"
                      + "?appid=\(WeChatSDK.appID)"
                      + "&secret=\(appSecret)"
                      + "&code=\(code)"
                      + "&grant_type=authorization_code"
        guard let url = URL(string: urlString) else {
            throw WeChatAuthError.tokenResponseInvalid("bad url")
        }
        let data: Data
        do {
            (data, _) = try await URLSession.shared.data(from: url)
        } catch {
            throw WeChatAuthError.tokenRequestFailed(error)
        }
        guard let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
              let accessToken = obj["access_token"] as? String,
              let openid = obj["openid"] as? String
        else {
            let raw = String(data: data, encoding: .utf8) ?? "<binary>"
            throw WeChatAuthError.tokenResponseInvalid(raw)
        }
        return TokenResponse(accessToken: accessToken, openid: openid)
    }

    private static func fetchUserInfo(accessToken: String, openid: String) async throws -> WeChatUser {
        let urlString = "https://api.weixin.qq.com/sns/userinfo"
                      + "?access_token=\(accessToken)"
                      + "&openid=\(openid)"
        guard let url = URL(string: urlString) else {
            throw WeChatAuthError.userInfoResponseInvalid("bad url")
        }
        let data: Data
        do {
            (data, _) = try await URLSession.shared.data(from: url)
        } catch {
            throw WeChatAuthError.userInfoRequestFailed(error)
        }
        guard let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
            let raw = String(data: data, encoding: .utf8) ?? "<binary>"
            throw WeChatAuthError.userInfoResponseInvalid(raw)
        }
        return WeChatUser(
            openid:     (obj["openid"]     as? String) ?? "",
            headimgurl: (obj["headimgurl"] as? String) ?? "",
            nickname:   (obj["nickname"]   as? String) ?? "",
            sex:        (obj["sex"]        as? Int)    ?? 0,
            // msext danbian: 去单引号（防 JSON 序列化炸）
            city:       danbian((obj["city"]     as? String) ?? ""),
            Province:   danbian((obj["province"] as? String) ?? ""),
            unionid:    (obj["unionid"]    as? String) ?? ""
        )
    }

    /// msext FuncPublic.danbian: 等价：去掉单引号
    private static func danbian(_ s: String) -> String {
        s.replacingOccurrences(of: "'", with: "")
    }
}
