import AVFoundation
import Foundation
import Observation

/// AVPlayer-based streaming player for cloud tracks.
/// Separate from AudioEngine (which uses AVAudioEngine for local files with EQ/BPM).
@MainActor
@Observable
final class StreamingPlayer {
    // MARK: - State

    var isPlaying = false
    var currentTime: TimeInterval = 0
    var duration: TimeInterval = 0
    var isBuffering = false

    var currentCloudTrack: ChadTrack?

    // MARK: - Callbacks

    @ObservationIgnored var onPlaybackFinished: (() -> Void)?
    @ObservationIgnored var onPlaybackError: ((String) -> Void)?

    // MARK: - Private

    @ObservationIgnored private var player: AVPlayer?
    @ObservationIgnored private var timeObserver: Any?
    @ObservationIgnored private var statusObservation: NSKeyValueObservation?
    @ObservationIgnored private var bufferObservation: NSKeyValueObservation?
    @ObservationIgnored private var didEndObserver: NSObjectProtocol?

    // MARK: - Audio Session (iOS)
    // Configure audio session for playback. We set .playback category but do NOT
    // deactivate on stop — the session stays active when switching between
    // StreamingPlayer and AudioEngine to avoid session thrashing.

    private func configureAudioSession() {
        do {
            let session = AVAudioSession.sharedInstance()
            if session.category != .playback {
                try session.setCategory(.playback, mode: .default, options: [])
            }
            if !session.isOtherAudioPlaying {
                try session.setActive(true)
            }
        } catch {
            print("StreamingPlayer: Failed to configure audio session: \(error)")
        }
    }

    // MARK: - Load & Play

    func loadAndPlay(track: ChadTrack, streamURL: URL, authHeaders: [String: String]) {
        cleanup()
        configureAudioSession()

        let asset = AVURLAsset(url: streamURL, options: [
            "AVURLAssetHTTPHeaderFieldsKey": authHeaders
        ])
        let playerItem = AVPlayerItem(asset: asset)
        let avPlayer = AVPlayer(playerItem: playerItem)

        self.player = avPlayer
        self.currentCloudTrack = track
        self.isBuffering = true

        statusObservation = playerItem.observe(\.status, options: [.new]) { [weak self] item, _ in
            Task { @MainActor in
                guard let self else { return }
                switch item.status {
                case .readyToPlay:
                    self.isBuffering = false
                    let cmDuration = item.duration
                    if cmDuration.isNumeric {
                        self.duration = CMTimeGetSeconds(cmDuration)
                    } else if let trackDuration = track.duration, trackDuration > 0 {
                        self.duration = trackDuration
                    } else {
                        self.duration = 1.0
                    }
                    avPlayer.play()
                    self.isPlaying = true
                case .failed:
                    self.isBuffering = false
                    let msg = item.error?.localizedDescription ?? "unknown error"
                    print("StreamingPlayer: Playback failed: \(msg)")
                    self.onPlaybackError?(msg)
                default:
                    break
                }
            }
        }

        bufferObservation = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new]) { [weak self] _, change in
            Task { @MainActor in
                self?.isBuffering = change.newValue ?? false
            }
        }

        let interval = CMTime(seconds: 1.0 / 30.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        timeObserver = avPlayer.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
            Task { @MainActor in
                guard let self, self.player != nil else { return }
                self.currentTime = CMTimeGetSeconds(time)
            }
        }

        didEndObserver = NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime,
            object: playerItem,
            queue: .main
        ) { [weak self] _ in
            Task { @MainActor in
                guard let self else { return }
                self.isPlaying = false
                self.currentTime = self.duration
                self.onPlaybackFinished?()
            }
        }
    }

    // MARK: - Transport

    func play() {
        player?.play()
        isPlaying = true
    }

    func pause() {
        player?.pause()
        isPlaying = false
    }

    func togglePlayPause() {
        if isPlaying { pause() } else { play() }
    }

    func stop() {
        cleanup()
        isPlaying = false
        currentTime = 0
        duration = 0
        currentCloudTrack = nil
    }

    func seek(to time: TimeInterval) {
        let cmTime = CMTime(seconds: time, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        player?.seek(to: cmTime, toleranceBefore: .zero, toleranceAfter: .zero)
        currentTime = time
    }

    func seek(by delta: TimeInterval) {
        let newTime = max(0, min(currentTime + delta, duration))
        seek(to: newTime)
    }

    var volume: Float {
        get { player?.volume ?? 0.8 }
        set { player?.volume = newValue }
    }

    // MARK: - Cleanup

    private func cleanup() {
        if let observer = timeObserver, let player {
            player.removeTimeObserver(observer)
        }
        timeObserver = nil
        statusObservation?.invalidate()
        statusObservation = nil
        bufferObservation?.invalidate()
        bufferObservation = nil
        if let observer = didEndObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        didEndObserver = nil
        player?.pause()
        player = nil
    }
}
