//
//  ScenicQueueRuntime.swift
//  suixinkan
//
//  Created by Codex on 2026/6/25.
//

import AVFoundation
import Combine
import SwiftUI
import UIKit

@MainActor
/// 排队运行时，页面外按开关短轮询当前打卡点并语音播报队列变化。
final class ScenicQueueRuntime: ObservableObject {
    @Published private(set) var isMonitoring = false
    @Published private(set) var lastPollText = "--"
    @Published private(set) var lastSpokenText = ""
    @Published private(set) var lastQueueCount = 0
    @Published private(set) var lastError: String?

    private let speaker = ScenicQueueSpeechService()
    private weak var api: (any ScenicQueueServing)?
    @Published private var userId: String?
    @Published private var scenicId: Int?
    @Published private var scenePhase: ScenePhase = .active
    @Published private var pollTask: Task<Void, Never>?
    @Published private var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid
    @Published private var announcementState = ScenicQueueAnnouncementState()
    @Published private var lastScenicId: Int?
    @Published private var lastSpotId: Int?
    @Published private var suspendedByQueueScreen = false

    /// 语音播报是否开启。
    var voiceEnabled: Bool {
        get {
            if UserDefaults.standard.object(forKey: ScenicQueueLocalSettings.ttsEnabledKey) == nil { return true }
            return UserDefaults.standard.bool(forKey: ScenicQueueLocalSettings.ttsEnabledKey)
        }
        set { UserDefaults.standard.set(newValue, forKey: ScenicQueueLocalSettings.ttsEnabledKey) }
    }

    /// 后台短时轮询是否开启。
    var backgroundPollingEnabled: Bool {
        get {
            if UserDefaults.standard.object(forKey: ScenicQueueLocalSettings.backgroundPollEnabledKey) == nil { return false }
            return UserDefaults.standard.bool(forKey: ScenicQueueLocalSettings.backgroundPollEnabledKey)
        }
        set { UserDefaults.standard.set(newValue, forKey: ScenicQueueLocalSettings.backgroundPollEnabledKey) }
    }

    /// 更新运行时上下文。
    func update(
        api: any ScenicQueueServing,
        userId: String?,
        scenicId: Int?,
        scenePhase: ScenePhase
    ) {
        self.api = api
        self.userId = userId
        self.scenicId = scenicId
        self.scenePhase = scenePhase

        guard !suspendedByQueueScreen else {
            stop()
            return
        }

        guard let scenicId else {
            stop()
            resetSnapshot()
            return
        }

        let spotId = selectedSpotId()
        guard spotId > 0 else {
            stop()
            lastError = "排队监听未启动：请先在排队设置里选择打卡点"
            return
        }

        if lastScenicId != scenicId || lastSpotId != spotId {
            resetSnapshot()
            lastScenicId = scenicId
            lastSpotId = spotId
        }

        if scenePhase == .background, !backgroundPollingEnabled {
            stop()
            return
        }

        startIfNeeded()
    }

    /// 页面自身接管实时监听时暂停运行时轮询。
    func setSuspendedByQueueScreen(_ suspended: Bool) {
        guard suspendedByQueueScreen != suspended else { return }
        suspendedByQueueScreen = suspended
        if suspended {
            stop()
        } else if let api {
            update(api: api, userId: userId, scenicId: scenicId, scenePhase: scenePhase)
        }
    }

    /// 停止运行时轮询和播报。
    func stop() {
        pollTask?.cancel()
        pollTask = nil
        isMonitoring = false
        speaker.stop()
        endBackgroundTask()
    }

    /// 立即轮询一次。
    func pollNow() {
        guard pollTask != nil else { return }
        Task { await pollOnce() }
    }

    private func startIfNeeded() {
        guard pollTask == nil else { return }
        isMonitoring = true
        if scenePhase == .background {
            beginBackgroundTask()
        }
        pollTask = Task { [weak self] in
            await self?.runLoop()
        }
    }

    private func runLoop() async {
        while !Task.isCancelled {
            await pollOnce()
            let seconds = pollIntervalSeconds()
            try? await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
        }
    }

    private func pollOnce() async {
        guard let api, let scenicId else { return }
        let spotId = selectedSpotId()
        guard spotId > 0 else { return }

        if scenePhase == .background {
            beginBackgroundTask()
        }

        do {
            if lastScenicId != scenicId || lastSpotId != spotId {
                resetSnapshot()
                lastScenicId = scenicId
                lastSpotId = spotId
            }
            async let statsData = api.scenicQueueStats(scenicId: scenicId, scenicSpotId: spotId)
            async let homeData = api.scenicQueueHome(
                scenicId: scenicId,
                scenicSpotId: spotId,
                type: QueueListType.queueing.rawValue,
                page: 1,
                pageSize: 20
            )
            let (stats, home) = try await (statsData, homeData)
            handle(stats: stats, tickets: home.list?.list ?? [])
            lastError = nil
        } catch {
            lastError = error.localizedDescription
        }
    }

    private func handle(stats: ScenicQueueStatsData, tickets: [ScenicQueueTicket]) {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        lastPollText = formatter.string(from: Date())
        lastQueueCount = stats.queueCount

        guard voiceEnabled else { return }
        let customText = ScenicQueueSettingsStore.customTtsText(userId: userId, scenicId: scenicId, spotId: lastSpotId)
        if let announcement = announcementState.nextAnnouncement(stats: stats, tickets: tickets, customCallText: customText) {
            lastSpokenText = announcement.text
            speaker.speak(announcement.text)
        }
    }

    private func pollIntervalSeconds() -> UInt64 {
        switch scenePhase {
        case .active:
            return 12
        case .inactive:
            return 20
        case .background:
            return 30
        @unknown default:
            return 20
        }
    }

    private func selectedSpotId() -> Int {
        ScenicQueueSettingsStore.selectedSpotId(userId: userId, scenicId: scenicId)
            ?? UserDefaults.standard.integer(forKey: ScenicQueueLocalSettings.selectedSpotIdKey)
    }

    private func resetSnapshot() {
        announcementState.reset()
        lastQueueCount = 0
        lastPollText = "--"
        lastSpokenText = ""
    }

    private func beginBackgroundTask() {
        guard backgroundTaskId == .invalid else { return }
        backgroundTaskId = UIApplication.shared.beginBackgroundTask(withName: "scenic.queue.poll") { [weak self] in
            Task { @MainActor in
                self?.endBackgroundTask()
                self?.stop()
            }
        }
    }

    private func endBackgroundTask() {
        guard backgroundTaskId != .invalid else { return }
        UIApplication.shared.endBackgroundTask(backgroundTaskId)
        backgroundTaskId = .invalid
    }
}

@MainActor
/// 排队语音播报服务。
final class ScenicQueueSpeechService: NSObject, AVSpeechSynthesizerDelegate {
    private let synthesizer = AVSpeechSynthesizer()
    private var pendingContinuations: [CheckedContinuation<Void, Never>] = []

    override init() {
        super.init()
        synthesizer.delegate = self
    }

    /// 播放语音文本。
    func speak(_ text: String) {
        let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !normalized.isEmpty else { return }
        enqueue(normalized, continuation: nil, replacePending: true)
    }

    /// 播放语音并等待结束，供测试和设置页试听使用。
    func speakAndWait(_ text: String) async {
        let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !normalized.isEmpty else { return }
        await withCheckedContinuation { continuation in
            enqueue(normalized, continuation: continuation, replacePending: false)
        }
    }

    /// 停止语音播报。
    func stop() {
        synthesizer.stopSpeaking(at: .immediate)
        resumePending()
    }

    private func enqueue(_ normalized: String, continuation: CheckedContinuation<Void, Never>?, replacePending: Bool) {
        if replacePending {
            synthesizer.stopSpeaking(at: .immediate)
            resumePending()
        }
        if let continuation {
            pendingContinuations.append(continuation)
        }
        let utterance = AVSpeechUtterance(string: normalized)
        utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
        utterance.rate = 0.48
        synthesizer.speak(utterance)
    }

    nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        Task { @MainActor in self.resumePending() }
    }

    nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
        Task { @MainActor in self.resumePending() }
    }

    private func resumePending() {
        let continuations = pendingContinuations
        pendingContinuations.removeAll()
        continuations.forEach { $0.resume() }
    }
}
