//
//  AliyunNLSTokenProvider.swift
//  suixinkan
//

import CryptoKit
import Foundation

/// 阿里云 NLS token 实体，表示语音服务访问凭证及过期时间。
struct AliyunNLSToken: Equatable {
    let id: String
    let expireTime: Int64?
}

/// 阿里云 NLS token 获取协议，便于 TTS 管理器测试替换。
protocol AliyunNLSTokenProviding {
    /// 使用临时 STS 凭证创建 NLS token。
    func createToken(credentials: AliyunOSSCredentials) async throws -> AliyunNLSToken
}

/// 阿里云 NLS token Provider，按 OpenAPI POP 签名请求 CreateToken。
final class AliyunNLSTokenProvider: AliyunNLSTokenProviding {
    typealias DataLoader = (URLRequest) async throws -> Data

    private let endpoint: URL
    private let dateProvider: () -> Date
    private let nonceProvider: () -> String
    private let dataLoader: DataLoader

    init(
        endpoint: URL = URL(string: "https://nls-meta.cn-shanghai.aliyuncs.com/")!,
        dateProvider: @escaping () -> Date = Date.init,
        nonceProvider: @escaping () -> String = { UUID().uuidString },
        dataLoader: @escaping DataLoader = { request in
            let (data, response) = try await URLSession.shared.data(for: request)
            if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
                throw AliyunNLSTokenError.httpStatus(http.statusCode)
            }
            return data
        }
    ) {
        self.endpoint = endpoint
        self.dateProvider = dateProvider
        self.nonceProvider = nonceProvider
        self.dataLoader = dataLoader
    }

    func createToken(credentials: AliyunOSSCredentials) async throws -> AliyunNLSToken {
        var params = signedBaseParams(credentials: credentials)
        params["Signature"] = Self.signature(params: params, accessKeySecret: credentials.accessKeySecret)
        let query = Self.canonicalQuery(params)
        guard let url = URL(string: endpoint.absoluteString + "?" + query) else {
            throw AliyunNLSTokenError.invalidURL
        }
        let request = URLRequest(url: url)
        let data = try await dataLoader(request)
        let response = try JSONDecoder().decode(AliyunNLSCreateTokenResponse.self, from: data)
        guard let id = response.token?.id?.trimmingCharacters(in: .whitespacesAndNewlines), !id.isEmpty else {
            throw AliyunNLSTokenError.missingToken
        }
        return AliyunNLSToken(id: id, expireTime: response.token?.expireTime)
    }

    /// 构造 CreateToken 的未签名公共参数。
    func signedBaseParams(credentials: AliyunOSSCredentials) -> [String: String] {
        [
            "AccessKeyId": credentials.accessKeyId,
            "Action": "CreateToken",
            "Format": "JSON",
            "RegionId": "cn-shanghai",
            "SignatureMethod": "HMAC-SHA1",
            "SignatureNonce": nonceProvider(),
            "SignatureVersion": "1.0",
            "Timestamp": Self.timestampFormatter.string(from: dateProvider()),
            "Version": "2019-02-28",
            "SecurityToken": credentials.securityToken,
        ]
    }

    /// 生成阿里云 POP HMAC-SHA1 签名。
    static func signature(params: [String: String], accessKeySecret: String) -> String {
        let canonical = canonicalQuery(params)
        let stringToSign = "GET&%2F&\(percentEncode(canonical))"
        let key = SymmetricKey(data: Data("\(accessKeySecret)&".utf8))
        let mac = HMAC<Insecure.SHA1>.authenticationCode(for: Data(stringToSign.utf8), using: key)
        return Data(mac).base64EncodedString()
    }

    /// 生成按参数名排序后的 OpenAPI 查询串。
    static func canonicalQuery(_ params: [String: String]) -> String {
        params
            .sorted { $0.key < $1.key }
            .map { "\(percentEncode($0.key))=\(percentEncode($0.value))" }
            .joined(separator: "&")
    }

    /// 阿里云 POP 签名要求的 RFC3986 编码。
    static func percentEncode(_ value: String) -> String {
        var allowed = CharacterSet.alphanumerics
        allowed.insert(charactersIn: "-_.~")
        return value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value
    }

    private static let timestampFormatter: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime]
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        return formatter
    }()
}

/// 阿里云 NLS token 获取错误。
enum AliyunNLSTokenError: Error, Equatable {
    case invalidURL
    case httpStatus(Int)
    case missingToken
}

private struct AliyunNLSCreateTokenResponse: Decodable {
    let token: TokenPayload?

    enum CodingKeys: String, CodingKey {
        case token = "Token"
    }

    struct TokenPayload: Decodable {
        let id: String?
        let expireTime: Int64?

        enum CodingKeys: String, CodingKey {
            case id = "Id"
            case expireTime = "ExpireTime"
        }
    }
}
