import AVFoundation
import Combine
import Foundation

/// 与安卓端 OralTrainerController 对齐的播放控制器。
/// 所有属性变更都发生在主线程（时间观察器、KVO、UI 回调均为主线程）。
final class PlayerController: ObservableObject {
    @Published private(set) var snapshot = PlaybackSnapshot()
    @Published private(set) var currentItem: TrainingMediaItem?
    @Published private(set) var currentSentence: SentenceBoundary?
    @Published var boundaryState: BoundaryState = .idle
    @Published var transientMessage: String?

    let player = AVPlayer()

    var config = PlayerConfig()
    var loopMode: LoopMode = .all

    private var mediaItem: TrainingMediaItem?
    private var lastSentenceIndex: Int?
    private var pendingSeekPositionMs: Int64?
    private var seekGeneration = 0
    private var stopAtSentenceEnd = false
    private var stopAtSentenceEndIndex: Int?
    private var speedBeforeLongPress: Float?
    private var transientTask: Task<Void, Never>?

    private var timeObserver: Any?
    private var endObserver: NSObjectProtocol?
    private var itemStatusObserver: NSKeyValueObservation?
    private var timeControlObserver: NSKeyValueObservation?

    init() {
        configureAudioSession()
        player.automaticallyWaitsToMinimizeStalling = true
        timeObserver = player.addPeriodicTimeObserver(
            forInterval: CMTime(seconds: 0.25, preferredTimescale: 600),
            queue: .main
        ) { [weak self] _ in
            self?.tick()
        }
        endObserver = NotificationCenter.default.addObserver(
            forName: AVPlayerItem.didPlayToEndTimeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.handleEnded()
        }
        timeControlObserver = player.observe(\.timeControlStatus, options: [.new]) { [weak self] _, _ in
            DispatchQueue.main.async {
                self?.tick()
            }
        }
        tick()
    }

    deinit {
        if let timeObserver = timeObserver {
            player.removeTimeObserver(timeObserver)
        }
        if let endObserver = endObserver {
            NotificationCenter.default.removeObserver(endObserver)
        }
        itemStatusObserver?.invalidate()
        timeControlObserver?.invalidate()
    }

    // MARK: - 装载

    func load(_ item: TrainingMediaItem) {
        lastSentenceIndex = nil
        pendingSeekPositionMs = nil
        stopAtSentenceEnd = false
        stopAtSentenceEndIndex = nil
        mediaItem = item
        currentItem = item
        boundaryState = .idle
        let playerItem = AVPlayerItem(url: item.url)
        attachItemObservers(to: playerItem)
        player.replaceCurrentItem(with: playerItem)
        if config.autoPlay {
            player.play()
        }
        tick()
    }

    // MARK: - 播放控制

    func play() {
        player.play()
    }

    func pause() {
        stopAtSentenceEnd = false
        stopAtSentenceEndIndex = nil
        player.pause()
    }

    func setVolume(_ volume: Float) {
        player.volume = volume
    }

    func togglePlayPause() {
        if player.timeControlStatus == .playing {
            pause()
        } else {
            play()
        }
    }

    func stop() {
        stopAtSentenceEnd = false
        stopAtSentenceEndIndex = nil
        player.pause()
        seek(to: .zero)
    }

    func seek(to positionMs: Int64) {
        stopAtSentenceEnd = false
        stopAtSentenceEndIndex = nil
        performSeek(to: positionMs)
    }

    func seekRelative(_ deltaMs: Int64) {
        seek(to: referencePositionMs() + deltaMs)
    }

    func rewind() {
        seekRelative(-max(0, config.defaultSeekStepMs))
    }

    func fastForward() {
        seekRelative(max(0, config.defaultSeekStepMs))
    }

    // MARK: - 按句跳转

    @discardableResult
    func seekToPreviousSentence() -> Bool {
        guard let item = mediaItem, !item.sentences.isEmpty else {
            return false
        }
        let position = referencePositionMs()
        guard let target = item.sentences.reversed().first(where: { $0.startMs < position - 250 })
            ?? item.sentences.first else {
            return false
        }
        seek(to: target.startMs)
        return true
    }

    @discardableResult
    func seekToNextSentence() -> Bool {
        guard let item = mediaItem, !item.sentences.isEmpty else {
            return false
        }
        let position = referencePositionMs()
        guard let target = item.sentences.first(where: { $0.startMs > position + 150 }) else {
            return false
        }
        seek(to: target.startMs)
        return true
    }

    func previousSentenceOrRewind() {
        if !config.sentenceMode || !seekToPreviousSentence() {
            rewind()
        }
    }

    func nextSentenceOrForward() {
        if !config.sentenceMode || !seekToNextSentence() {
            fastForward()
        }
    }

    /// 只播放当前句子，播完自动暂停并回到句首（评测“播放原句”）。
    func playCurrentSentenceAndStop() {
        guard let sentence = currentSentence else {
            return
        }
        performSeek(to: sentence.startMs)
        stopAtSentenceEnd = true
        stopAtSentenceEndIndex = sentence.index
        play()
    }

    // MARK: - 设置

    func setContinuousPlayback(_ enabled: Bool) {
        config.continuousPlayback = enabled
    }

    func setSentenceMode(_ enabled: Bool) {
        config.sentenceMode = enabled
    }

    func setLoopMode(_ mode: LoopMode) {
        loopMode = mode
    }

    func setPlaybackSpeed(_ speed: Float) {
        let clamped = min(config.maxPlaybackSpeed, max(config.minPlaybackSpeed, speed))
        if player.timeControlStatus == .playing {
            player.rate = clamped
        } else {
            player.defaultRate = clamped
        }
        tick()
    }

    // MARK: - 长按变速（与安卓端 GestureControlsConfig 一致）

    func beginLongPressSpeed(atX x: CGFloat, width: CGFloat) {
        speedBeforeLongPress = snapshot.speed
        setPlaybackSpeed(speedForTouchX(x, width: width))
        showTransientMessage("变速播放中")
    }

    func updateLongPressSpeed(atX x: CGFloat, width: CGFloat) {
        setPlaybackSpeed(speedForTouchX(x, width: width))
    }

    func endLongPressSpeed() {
        if let speed = speedBeforeLongPress {
            setPlaybackSpeed(speed)
        }
        speedBeforeLongPress = nil
    }

    private func speedForTouchX(_ x: CGFloat, width: CGFloat) -> Float {
        guard width > 0 else {
            return 1
        }
        let center = width / 2
        let distanceRatio = abs(x - center) / center
        let farFromCenter = distanceRatio >= 0.5
        if x < center {
            return farFromCenter ? 0.5 : 0.75
        }
        return farFromCenter ? 2.0 : 1.5
    }

    // MARK: - 手势消息

    func handleGesture(_ kind: GestureKind) {
        switch kind {
        case .singleTap:
            togglePlayPause()
            showTransientMessage("播放状态已切换")
        case .swipeLeft:
            previousSentenceOrRewind()
            showTransientMessage("已跳到上一句")
        case .swipeRight:
            nextSentenceOrForward()
            showTransientMessage("已跳到下一句")
        case .longPressSpeed:
            break
        }
    }

    func showTransientMessage(_ message: String) {
        transientMessage = message
        transientTask?.cancel()
        transientTask = Task { @MainActor [weak self] in
            try? await Task.sleep(nanoseconds: 1_500_000_000)
            guard !Task.isCancelled else {
                return
            }
            self?.transientMessage = nil
        }
    }

    // MARK: - 状态刷新

    private func tick() {
        guard let item = player.currentItem else {
            snapshot = PlaybackSnapshot(mediaId: mediaItem?.id, state: .idle, speed: currentSpeed())
            return
        }
        let position = referencePositionMs()
        if pendingSeekPositionMs == nil, lastSentenceIndex == nil {
            lastSentenceIndex = sentence(at: position)?.index
        }
        if pendingSeekPositionMs == nil {
            enforceSentenceBoundary(positionMs: position)
        }
        let finalPosition = referencePositionMs()
        let sentence = sentence(at: finalPosition)
        if sentence?.index != lastSentenceIndex {
            lastSentenceIndex = sentence?.index
        }
        let duration = durationMs(of: item)
        let state = playbackState(of: item, positionMs: finalPosition, durationMs: duration)
        snapshot = PlaybackSnapshot(
            mediaId: mediaItem?.id,
            positionMs: finalPosition,
            durationMs: duration,
            bufferedPositionMs: bufferedPositionMs(of: item),
            isPlaying: player.timeControlStatus == .playing,
            state: state,
            speed: currentSpeed(),
            sentenceIndex: sentence?.index
        )
        currentSentence = sentence
    }

    private func currentPositionMs() -> Int64 {
        let seconds = player.currentTime().seconds
        guard seconds.isFinite else {
            return 0
        }
        return max(0, Int64(seconds * 1000))
    }

    /// 连续手势/快进快退时使用 pending seek 的目标位置，避免仍基于旧的播放位置计算。
    private func referencePositionMs() -> Int64 {
        pendingSeekPositionMs ?? currentPositionMs()
    }

    /// AVPlayer 的 seek 是异步的；所有 seek 必须统一走这里，避免播放循环、
    /// 用户手势和 seek 完成回调交错后把位置拉回旧句子。
    private func performSeek(to positionMs: Int64) {
        let targetPositionMs = max(0, positionMs)
        seekGeneration += 1
        let generation = seekGeneration
        pendingSeekPositionMs = targetPositionMs
        // 立即切换句子锚点。否则 periodic observer 仍可能以旧句子为锚点，
        // 在 seek 完成前后把新位置拉回旧句开头。
        lastSentenceIndex = sentence(at: targetPositionMs)?.index
        let time = CMTime(seconds: Double(targetPositionMs) / 1000, preferredTimescale: 600)
        player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] _ in
            DispatchQueue.main.async {
                guard let self, self.seekGeneration == generation else {
                    return
                }
                self.pendingSeekPositionMs = nil
                self.tick()
            }
        }
        tick()
    }

    private func durationMs(of item: AVPlayerItem) -> Int64 {
        let seconds = item.duration.seconds
        guard seconds.isFinite, seconds > 0 else {
            return -1
        }
        return Int64(seconds * 1000)
    }

    private func bufferedPositionMs(of item: AVPlayerItem) -> Int64 {
        guard let range = item.loadedTimeRanges.last?.timeRangeValue else {
            return 0
        }
        let seconds = range.end.seconds
        guard seconds.isFinite else {
            return 0
        }
        return max(0, Int64(seconds * 1000))
    }

    private func currentSpeed() -> Float {
        let rate = player.rate
        return rate == 0 ? player.defaultRate : rate
    }

    private func playbackState(of item: AVPlayerItem, positionMs: Int64, durationMs: Int64) -> PlaybackState {
        if item.status == .failed {
            return .idle
        }
        switch player.timeControlStatus {
        case .waitingToPlayAtSpecifiedRate:
            return .buffering
        case .playing:
            return .ready
        case .paused:
            if durationMs > 0, positionMs >= durationMs {
                return .ended
            }
            return .ready
        @unknown default:
            return .idle
        }
    }

    private func sentence(at positionMs: Int64) -> SentenceBoundary? {
        guard let item = mediaItem else {
            return nil
        }
        return item.sentences.last { sentence in
            positionMs >= sentence.startMs && positionMs < sentence.endMs
        }
    }

    /// 与安卓端一致：非连续播放时到句尾回到句首；playCurrentSentenceAndStop 播完自动暂停。
    private func enforceSentenceBoundary(positionMs: Int64) {
        guard config.sentenceMode,
              player.timeControlStatus == .playing,
              let item = mediaItem,
              !item.sentences.isEmpty else {
            return
        }
        if stopAtSentenceEnd {
            guard let index = stopAtSentenceEndIndex,
                  let sentence = item.sentences.first(where: { $0.index == index }) else {
                return
            }
            if positionMs >= sentence.endMs {
                stopAtSentenceEnd = false
                stopAtSentenceEndIndex = nil
                player.pause()
                seekRaw(sentence.startMs)
            }
            return
        }
        if config.continuousPlayback {
            return
        }
        guard let anchorIndex = lastSentenceIndex,
              let anchor = item.sentences.first(where: { $0.index == anchorIndex }) else {
            return
        }
        if positionMs >= anchor.endMs {
            seekRaw(anchor.startMs)
        }
    }

    private func seekRaw(_ positionMs: Int64) {
        performSeek(to: positionMs)
    }

    private func handleEnded() {
        guard loopMode != .off else {
            tick()
            return
        }
        performSeek(to: .zero)
        player.play()
        tick()
    }

    // MARK: - 观察器

    private func attachItemObservers(to item: AVPlayerItem) {
        itemStatusObserver?.invalidate()
        itemStatusObserver = item.observe(\.status, options: [.new]) { [weak self] item, _ in
            DispatchQueue.main.async {
                guard let self = self else {
                    return
                }
                if item.status == .failed {
                    let message = item.error?.localizedDescription ?? "未知错误"
                    self.showTransientMessage("播放失败：\(message)")
                }
                self.tick()
            }
        }
    }

    private func configureAudioSession() {
        let session = AVAudioSession.sharedInstance()
        try? session.setCategory(.playback, mode: .moviePlayback)
        try? session.setActive(true)
    }
}
