//
//  RemoteAudioHandler.swift
//  ylgamehall
//
//  H5 → Native handlers：prepareaudio / mediaTypeAudio
//  契约：docs/H5-Native-Contract.md §3.1 ［4］［5］ + §3.2 ［7］［8］［9］
//
//  ## prepareaudio 完整链路（Phase 3.E）
//
//    H5 调用 → 权限检查 →
//      ├─ 拒绝：弹 Alert，不触发 cb（对齐 daoqi NewRootVC.m:314-319）
//      └─ 同意：RecordingPresenter 启动录音浮层 + 立即回 cb
//                                            ↓ 用户松手 / 60s
//                                          录音完成 (wavURL)
//                                            ↓
//                              AVAudioPlayer 读 duration → round 取整秒
//                                            ↓
//                              生成 amr 文件名（大厅 / 子游戏不同）
//                                            ↓
//                              AMRCodec.wavToAmr → 七牛上传
//                                            ↓
//                              反向 callback getaudiourl + 子游戏额外 recordSuccess
//
//  ## mediaTypeAudio（Phase 3.F 实现，本提交仍为 stub）
//
//  本文件仍保留 stub；Phase 3.F 会替换为完整链路（下载 amr → amrToWav →
//  VoicePlayer.play + gameui_play_voice / gameui_stop_voice）。
//

import Foundation
import AVFoundation
import UIKit
import os.log

public enum RemoteAudioHandler {

    private static let log = Logger(subsystem: "ylgamehall", category: "RemoteAudio")

    /// 注册 prepareaudio / mediaTypeAudio。
    /// - Parameters:
    ///   - bridge: 桥
    ///   - hostVC: 录音浮层挂载的宿主 VC（lobby = WebContainerViewController，
    ///     subGame = SubGameViewController）。每次录音重新求值，避免捕获过时引用
    ///   - isSubGame: 是否子游戏。子游戏录音成功后额外触发 recordSuccess
    ///     反向 callback（对齐 daoqi gameController.m:2310；大厅 NewRootVC 不触发）
    public static func register(
        on bridge: any BridgeProtocol,
        hostVC: @escaping @MainActor @Sendable () -> UIViewController?,
        isSubGame: Bool
    ) {
        // 【4】prepareaudio — 完整实现（Phase 3.E）
        //   cb: "Response from prepareaudio"（仅录音启动成功后触发；
        //   权限拒绝 / 启动失败时不触发，对齐 daoqi 行为）
        bridge.register("prepareaudio") { _, callback in
            Task { @MainActor in
                await handlePrepareAudio(
                    bridge: bridge,
                    hostVC: hostVC,
                    isSubGame: isSubGame,
                    callback: callback
                )
            }
        }

        // 【5】mediaTypeAudio — 完整实现（Phase 3.F）
        //   cb: "Response from mediaTypeAudio"（在下载+转码完成后触发，对齐 daoqi
        //   同步阻塞下载语义；下载失败时不触发 cb）
        bridge.register("mediaTypeAudio") { data, callback in
            Task { @MainActor in
                await handleMediaTypeAudio(
                    data: data,
                    bridge: bridge,
                    callback: callback
                )
            }
        }
    }

    // MARK: - prepareaudio 处理（MainActor）

    @MainActor
    private static func handlePrepareAudio(
        bridge: any BridgeProtocol,
        hostVC: @MainActor @Sendable () -> UIViewController?,
        isSubGame: Bool,
        callback: BridgeCallback?
    ) async {
        // 1. 麦克风权限**只读**状态检查（对齐 daoqi FuncPublic.m:1144-1170 ifauth 实现）。
        //    主动请求权限（requestRecordPermission）会立即弹出 iOS 系统对话框；
        //    在 H5 按住状态下弹系统对话框 → 用户必须松手点"允许" →
        //    RecordingAwareWindow.touchObserver 捕获松手立刻 stop → 浮层瞬间关闭，
        //    用户体验异常（首次启动 app 录音永远失败）。
        let status = AVCaptureDevice.authorizationStatus(for: .audio)
        switch status {
        case .denied, .restricted:
            // 明确拒绝 → 弹 Alert + 不触发 cb（对齐 daoqi NewRootVC.m:314-319）
            showMicrophonePermissionAlert(on: hostVC())
            return

        case .notDetermined:
            // 首次启动：弹系统权限对话框，**不**启动录音浮层（避免与 touchObserver
            // 死锁——touchObserver 会捕获用户松手点"允许"的动作立刻 stop 录音）。
            // 用户做完选择 / 第二次按录音按钮时走 .authorized 分支正常录音。
            //
            // 优先用 iOS 17+ 的 AVAudioApplication.requestRecordPermission()
            // （原生 async，Swift Concurrency 一等公民）；iOS 26+ 上
            // deprecated 的 AVAudioSession.requestRecordPermission(_:) +
            // withCheckedContinuation 桥接路径实测会触发 Thread 10
            // EXC_BREAKPOINT（疑似系统内部桥接新 API 时状态机异常）。
            _ = await requestMicrophonePermission()
            // ⚠️ 必须触发 cb（对齐 daoqi ifauth==1 路径行为，daoqi NewRootVC.m:322
            // 在 [recorderVC beginRecordByFileName:] 后总是 responseCallback）。
            //
            // 不触发 cb 的后果：H5 业务在 callHandler 调用后通常会上"录音中"业务锁
            // （加触摸遮罩、禁用按钮等），等收到 cb 才解锁。系统权限对话框已经把
            // H5 的 in-flight touch 打断为 touchcancel，H5 业务的 touchcancel 处理
            // 可能不完善 → 业务锁不释放 → 整个 H5 后续无法点击。
            //
            // 触发 cb 让 H5 释放业务锁，即使本次实际未录音也无害（H5 会按"无
            // getaudiourl"的失败路径走，与 daoqi 转码失败 silent 路径等价）。
            callback?(.string("Response from prepareaudio"))
            return

        case .authorized:
            break  // 继续启动录音

        @unknown default:
            return
        }

        // 2. 启动录音浮层
        guard let vc = hostVC() else {
            log.error("prepareaudio: no host VC")
            return
        }
        let fileName = VoiceRecorder.currentTimeString()
        do {
            try RecordingPresenter.start(on: vc, fileName: fileName) { wavURL, _ in
                // 录音完成回调（@MainActor）。后续转码 / 上传是 IO 重活，挪到
                // 后台 Task 避免阻塞主线程
                Task.detached(priority: .userInitiated) {
                    await processCompletedRecording(
                        wavURL: wavURL,
                        bridge: bridge,
                        isSubGame: isSubGame
                    )
                }
            }
        } catch {
            log.error("prepareaudio start failed: \(error.localizedDescription, privacy: .public)")
            return  // 启动失败不触发 cb（对齐 daoqi handler 内 return 行为）
        }

        // 3. 启动成功，立即触发 callback（对齐 daoqi NewRootVC.m:322
        //    [recorderVC beginRecordByFileName:..] 之后 responseCallback）
        callback?(.string("Response from prepareaudio"))
    }

    // MARK: - 录音完成后处理（后台 Task）

    private static func processCompletedRecording(
        wavURL: URL,
        bridge: any BridgeProtocol,
        isSubGame: Bool
    ) async {
        // 1. 读 wav duration → round 取整秒（对齐 daoqi NewRootVC.m:1891-1893
        //    AVAudioPlayer.duration + round 行为）
        let durationSec: Int
        do {
            let probe = try AVAudioPlayer(contentsOf: wavURL)
            durationSec = Int(probe.duration.rounded())
        } catch {
            log.error("read wav duration failed: \(error.localizedDescription, privacy: .public)")
            return
        }

        // 2. 生成上传文件名（大厅 vs 子游戏命名规则不同）
        let amrFileName = uploadFileName(isSubGame: isSubGame)
        let amrURL = wavURL.deletingLastPathComponent().appendingPathComponent(amrFileName)

        // 3. WAV → AMR 转码
        do {
            try AMRCodec.wavToAmr(wavURL, to: amrURL)
        } catch {
            log.error("wav→amr failed: \(error.localizedDescription, privacy: .public)")
            return
        }

        // 4. 七牛上传（key = amrURL.lastPathComponent，由 QiniuUploader 取）
        let uploaded: UploadedFile
        do {
            uploaded = try await QiniuUploader.shared.upload(amrURL, timeSec: durationSec)
        } catch {
            log.error("qiniu upload failed: \(error.localizedDescription, privacy: .public)")
            return
        }

        // 5. 反向 callback（在 MainActor）
        await MainActor.run {
            // getaudiourl
            // ⚠️ time 是 String（%ld 整秒），不是 number — 对齐 daoqi
            //    NewRootVC.m:1923 / gameController.m:2305
            bridge.call(
                "getaudiourl",
                data: .object([
                    "audiourl": .string(uploaded.fileUrl),
                    "time": .string(String(durationSec))
                ]),
                callback: nil
            )
            log.debug("getaudiourl audiourl=\(uploaded.fileUrl, privacy: .public) time=\(durationSec, privacy: .public)")

            // 子游戏额外 recordSuccess（对齐 daoqi gameController.m:2310；
            // 大厅 NewRootVC 无此 callHandler）
            if isSubGame {
                bridge.call(
                    "recordSuccess",
                    data: .object([
                        "fileUrl": .string(uploaded.fileUrl),
                        "fileName": .string(uploaded.fileName),
                        "fileKey": .string(uploaded.fileKey)
                    ]),
                    callback: nil
                )
                log.debug("recordSuccess fileKey=\(uploaded.fileKey, privacy: .public)")
            }
        }
    }

    // MARK: - 工具

    /// 上传文件名（同时是七牛 key、本地 .amr 文件名）：
    ///   大厅：`yyyyMMddHHmmss%08X.amr`     （无下划线，对齐 daoqi NewRootVC.m:1908）
    ///   子游戏：`yyyyMMddHHmmss_%08X.amr`   （有下划线，对齐 daoqi gameController.m:2269）
    private static func uploadFileName(isSubGame: Bool) -> String {
        let timestamp = VoiceRecorder.currentTimeString()
        let random = String(format: "%08X", arc4random())
        let separator = isSubGame ? "_" : ""
        return "\(timestamp)\(separator)\(random).amr"
    }

    // MARK: - mediaTypeAudio 处理（MainActor）

    @MainActor
    private static func handleMediaTypeAudio(
        data: BridgeData?,
        bridge: any BridgeProtocol,
        callback: BridgeCallback?
    ) async {
        // 1. 解析入参（核心字段 audiourl + user，对齐 daoqi NewRootVC.m:326-328 /
        //    gameController.m:435-437；H5 实测也会带 type 字段但 daoqi 注释掉、不用）。
        //
        //    ⚠️ user 字段在 H5 实测中是 number（如 0、12345），不是 string；用
        //    asLooseString 兼容两种类型（对齐 daoqi `%@` 格式化容忍 NSNumber 行为）
        guard let obj = data?.asObject,
              let urlString = obj["audiourl"]?.asString,
              let user = obj["user"]?.asLooseString,
              let downloadURL = URL(string: urlString)
        else {
            log.error("mediaTypeAudio: bad params \(String(describing: data), privacy: .public)")
            return  // 参数不全 → 不触发 cb（对齐 daoqi 入参空时走到 file 不存在 return）
        }

        // 2. 注册 VoicePlayer.onFinish（每次 mediaTypeAudio 更新，绑定当前 bridge；
        //    [weak bridge] 让 bridge 释放后 onFinish 自动失效）。
        //    onFinish 触发时机：自然结束 / 解码失败 / 被新一次 play 打断；
        //    payload `finishedUser` 是被结束 / 被打断那条语音绑定的 user
        //    （由 VoicePlayer 内部 currentUser 跟踪），不是入参里最新的 user。
        VoicePlayer.shared.onFinish = { [weak bridge] finishedUser in
            Task { @MainActor in
                bridge?.call(
                    "gameui_stop_voice",
                    data: .string(finishedUser),
                    callback: nil
                )
            }
        }

        // 3. 异步下载 AMR
        let amrData: Data
        do {
            let (d, _) = try await URLSession.shared.data(from: downloadURL)
            amrData = d
        } catch {
            log.error("mediaTypeAudio download failed: \(error.localizedDescription, privacy: .public)")
            return  // 下载失败 → 不触发 cb（对齐 daoqi NewRootVC.m:335-337
                    // `if (![[NSFileManager defaultManager] fileExistsAtPath:filename]) return;`）
        }

        // 4. 写到沙盒 .amr 临时文件
        let tempName = VoiceRecorder.currentTimeString()
        let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let amrLocal = docs.appendingPathComponent(tempName).appendingPathExtension("amr")
        try? FileManager.default.removeItem(at: amrLocal)
        do {
            try amrData.write(to: amrLocal)
        } catch {
            log.error("mediaTypeAudio write amr failed: \(error.localizedDescription, privacy: .public)")
            return
        }

        // 5. AMR → WAV 转码
        //    daoqi 文件名约定：tempName + "_AmrToWav.wav"（NewRootVC.m:341）
        let wavLocal = docs
            .appendingPathComponent("\(tempName)_AmrToWav")
            .appendingPathExtension("wav")
        try? FileManager.default.removeItem(at: wavLocal)
        let transcoded: Bool
        do {
            try AMRCodec.amrToWav(amrLocal, to: wavLocal)
            transcoded = true
        } catch {
            // 转码失败：log，**但 cb 仍触发**（对齐 daoqi NewRootVC.m:355-357
            // 失败仅 NSLog 不影响 responseCallback 触发）
            log.error("mediaTypeAudio amr→wav failed: \(error.localizedDescription, privacy: .public)")
            transcoded = false
        }

        // 6. voicePlaying 总开关 + 实际播放 + gameui_play_voice
        //    （仅转码成功 + 开关 enabled 时触发，对齐 daoqi
        //    NewRootVC.m:343-353 if (canbofang) { play + callHandler }）。
        //    若当前已有正在播放的 player，VoicePlayer.play 会先停旧 player 并
        //    通过 onFinish 反向调 gameui_stop_voice 让 H5 停旧动画，再起新播放。
        let canPlay = await VoiceCenter.shared.isEnabled
        log.debug("mediaTypeAudio user=\(user, privacy: .public) transcoded=\(transcoded, privacy: .public) canPlay=\(canPlay, privacy: .public)")
        if transcoded, canPlay {
            do {
                try VoicePlayer.shared.play(wavLocal, user: user)
                // gameui_play_voice 数据是 user 字符串（非 dict）
                bridge.call(
                    "gameui_play_voice",
                    data: .string(user),
                    callback: nil
                )
                log.debug("gameui_play_voice user=\(user, privacy: .public)")
            } catch {
                log.error("VoicePlayer.play failed: \(error.localizedDescription, privacy: .public)")
            }
        }

        // 7. 触发 mediaTypeAudio cb（对齐 daoqi NewRootVC.m:357，**总是**触发
        //    即使转码失败 / canbofang=NO 都触发；仅下载失败的 return 路径不触发）
        callback?(.string("Response from mediaTypeAudio"))
    }

    /// 麦克风权限请求（弹系统对话框）。
    ///
    /// iOS 17+ 用新 `AVAudioApplication.requestRecordPermission()`（原生 async）；
    /// iOS 15.6 / 16 用 deprecated 的 `AVAudioSession.requestRecordPermission(_:)` +
    /// withCheckedContinuation 桥接。
    ///
    /// **不用** deprecated API 走新 SDK 路径的原因：实测 iOS 26.5 SDK 上 deprecated
    /// API 内部桥接到 AVAudioApplication 时与 withCheckedContinuation 的状态机交互
    /// 异常，会在 Thread 10 触发 EXC_BREAKPOINT（疑似系统 bug 或 Swift Concurrency
    /// 与 ObjC 桥接的 corner case）。新 API 直接 await 不经 Continuation 状态机，
    /// 行为稳定。
    private static func requestMicrophonePermission() async -> Bool {
        if #available(iOS 17.0, *) {
            return await AVAudioApplication.requestRecordPermission()
        } else {
            return await withCheckedContinuation { (cont: CheckedContinuation<Bool, Never>) in
                AVAudioSession.sharedInstance().requestRecordPermission { granted in
                    cont.resume(returning: granted)
                }
            }
        }
    }

    @MainActor
    private static func showMicrophonePermissionAlert(on hostVC: UIViewController?) {
        // 用户已拒绝麦克风权限时调用。iOS 不允许再次主动弹系统权限对话框
        // （requestRecordPermission 在 .denied 状态下不再弹窗、直接返回 false），
        // 唯一恢复路径是用户手动去「设置 → app → 麦克风」开启。
        //
        // 文案保留 daoqi NewRootVC.m:316 的引导语；按钮提供「去授权」直接跳
        // app 设置页（比 daoqi 仅"确定"更易用），「取消」关闭 Alert。
        let alert = UIAlertController(
            title: nil,
            message: "需要访问您的麦克风,请启用麦克风-设置/隐私/麦克风!",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "去授权", style: .default) { _ in
            // 跳转到本 app 在「设置」中的页面。用户在此页打开麦克风开关后回到
            // app，状态自动变为 .authorized，下次 prepareaudio 走正常分支。
            if let url = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(url)
            }
        })
        hostVC?.present(alert, animated: true)
    }
}
