//
//  QiniuUploader.swift
//  ylgamehall
//
//  七牛上传 actor wrapper。包 `QNUploadManager` 为 async/await。
//
//  ⚠️ canImport(QiniuSDK) 守卫：在 Xcode 把 https://github.com/qiniu/objc-sdk
//  添加到 Swift Package Dependencies 之前，本文件编译为 no-op，upload 永远抛
//  QiniuUploadError.sdkNotLinked。后续 handler 可降级到 stub 路径。
//
//  上传完成后返回 UploadedFile，字段映射给 H5 `getaudiourl` / `recordSuccess`。
//

import Foundation

#if canImport(QiniuSDK)
import QiniuSDK
#endif

public struct UploadedFile: Sendable {
    public let fileUrl:  String   // 公网访问 URL（http://{domain}/{key}）
    public let fileName: String   // 原始本地文件名
    public let fileKey:  String   // 七牛 key（path-on-cdn）
    public let timeSec:  Int      // 录音时长（秒）
}

public enum QiniuUploadError: Error, Sendable {
    case sdkNotLinked
    case fileNotFound(String)
    case uploadFailed(any Error)
    case responseInvalid
}

public actor QiniuUploader {

    public static let shared = QiniuUploader()

    public init() {}

    /// 上传文件。
    /// - Parameters:
    ///   - localFile: 本地文件 URL（amr 录音文件）
    ///   - timeSec: 录音时长，回传给 H5 `getaudiourl` 用
    /// - Returns: UploadedFile（含公网 URL）
    public func upload(_ localFile: URL, timeSec: Int) async throws -> UploadedFile {
        #if canImport(QiniuSDK)
        let fm = FileManager.default
        guard fm.fileExists(atPath: localFile.path) else {
            throw QiniuUploadError.fileNotFound(localFile.path)
        }

        // 七牛 key 直接用本地文件名（调用方负责命名）。对齐 daoqi 录音文件命名规则：
        //   - 大厅：yyyyMMddHHmmss%08X.amr（NewRootVC.m:1908，无下划线）
        //   - 子游戏：yyyyMMddHHmmss_%08X.amr（gameController.m:2269，有下划线）
        // H5 通过 audiourl 转发给对端，对端按文件名规律假设——必须严格对齐。
        let key = QiniuConfig.recordingDirectory + localFile.lastPathComponent

        // 客户端自签 token（参 QiniuTokenSigner / msext QiniuManager 同款算法）
        let token = await QiniuTokenSigner.uploadToken(key: key)
        // 预取 CDN 域名（远端注入到 QiniuConfig actor），闭包里同步拼 URL 避免再 await
        let cdnDomain = await QiniuConfig.shared.cdnDomain

        // 七牛 SDK 8.x：`QNUploadManager.init()` 被标 `kQNDeprecated`，必须走
        // `initWithConfiguration:`。用 defaultConfigurationV2（v2 已替代旧 defaultConfiguration）。
        // initWithConfiguration: ObjC 端 instancetype 桥到 Swift 仍为可选；
        // 实际几乎不会失败（内部仅初始化无 IO），失败按 responseInvalid 抛出。
        let cfg = QNConfiguration.defaultConfigurationV2()
        guard let mgr = QNUploadManager(configuration: cfg) else {
            throw QiniuUploadError.responseInvalid
        }
        let fileName = localFile.lastPathComponent

        return try await withCheckedThrowingContinuation { (cont: CheckedContinuation<UploadedFile, Error>) in
            mgr.putFile(
                localFile.path,
                key: key,
                token: token,
                complete: { info, savedKey, _ in
                    if let info, info.isOK, let savedKey {
                        let url = "http://\(cdnDomain)/\(savedKey)"
                        cont.resume(returning: UploadedFile(
                            fileUrl:  url,
                            fileName: fileName,
                            fileKey:  savedKey,
                            timeSec:  timeSec
                        ))
                    } else if let error = info?.error {
                        cont.resume(throwing: QiniuUploadError.uploadFailed(error))
                    } else {
                        cont.resume(throwing: QiniuUploadError.responseInvalid)
                    }
                },
                option: nil
            )
        }
        #else
        throw QiniuUploadError.sdkNotLinked
        #endif
    }

    /// 取消所有进行中任务（接 BackGameDataHandler 清理钩子用）。
    /// QNUploadManager 没有「取消全部」API，单任务用 QNUploadOption.cancellationSignal 实现；
    /// 当前实现以 task 粒度由 Swift Task.cancel 传递（withCheckedThrowingContinuation
    /// 已在 cancellation 时 throw CancellationError），无需额外状态。
    public func cancelInFlight() {
        // no-op（依赖 Task.cancel 传播）；如未来需要硬 cancel，改为持有 QNUploadOption 并触发 cancellationSignal
    }
}
