//
//  ContentPlayerPlayManager.swift
//  SodaLive
//
//  Created by klaus on 12/16/24.
//

import Foundation
import AVKit
import MediaPlayer
import Combine

import Kingfisher

final class ContentPlayerPlayManager: NSObject, ObservableObject {
    enum LoopState {
        case idle
        case waitingForEnd(start: CMTime)
        case looping(start: CMTime, end: CMTime)
    }
    
    static let shared = ContentPlayerPlayManager()
    
    private let repository = ContentGenerateUrlRepository()
    
    @Published var id = 0
    @Published var title = ""
    @Published var nickname = ""
    @Published var coverImageUrl = ""
    @Published var creatorProfileUrl = ""
    
    @Published private(set) var isShowingMiniPlayer = false
    @Published private(set) var isPlaying = false
    
    @Published var isLoading = false
    @Published var errorMessage = ""
    @Published var isShowPopup = false
    @Published var isShowPlaylist = false
    @Published var playlist: [AudioContentPlaylistContent] = []
    @Published var currentTime: Double = 0.0
    @Published private(set) var duration: Double = 0.0
    @Published var isEditing = false
    
    @Published private(set) var loopState: LoopState = .idle
    @Published private var timeObserver: Any?
    
    var player: AVPlayer?
    private var cancellables = Set<AnyCancellable>()
    
    @Published var bufferedTime: Double = 0 // 현재 버퍼링된 시간
    @Published var isPlaybackLikelyToKeepUp: Bool = false // 재생 가능 상태
    
    let minimumBufferedTime: Double = 5.0
    
    var playlistManager: AudioContentPlaylistManager? = nil
    
    override init() {
        self.player = AVPlayer()
        
        do {
            let audioSession = AVAudioSession.sharedInstance()
            try audioSession.setCategory(.playback, mode: .moviePlayback)
            try audioSession.setActive(true)
        } catch {
            DEBUG_LOG("Audio Session 설정 실패: \(error.localizedDescription)")
        }
        
        super.init()
    }
    
    func setPlaylist(playlist: [AudioContentPlaylistContent]) {
        resetPlayer()
        self.registerRemoteControlEvents()
        self.playlist = playlist
        playlistManager = AudioContentPlaylistManager(playlist: playlist)
        playNextContent()
        isShowingMiniPlayer = true
        UIApplication.shared.beginReceivingRemoteControlEvents()
    }
    
    private func setupPlayer(with url: URL) {
        stopLooping()
        loopState = .idle
        // 기존 playerItem 관련 Combine 구독 해제
        cancellables.removeAll()
        
        // 새로운 AVPlayerItem 생성
        let playerItem = AVPlayerItem(url: url)
        self.player?.replaceCurrentItem(with: playerItem)
        self.player?
            .periodicTimePublisher(interval: CMTime(seconds: 0.5, preferredTimescale: 600))
            .sink { [weak self] currentTime in
                if !(self?.isEditing ?? false) {
                    self?.currentTime = CMTimeGetSeconds(currentTime)
                }
            }
            .store(in: &cancellables)
        
        playerItem.publisher(for: \.duration)
            .map { CMTimeGetSeconds($0) }
            .filter { !$0.isNaN } // NaN 방지
            .sink { [weak self] duration in
                self?.duration = duration
            }
            .store(in: &cancellables)
        
        playerItem.publisher(for: \.loadedTimeRanges)
            .compactMap { $0.first?.timeRangeValue }
            .map { CMTimeGetSeconds($0.start) + CMTimeGetSeconds($0.duration) }
            .receive(on: DispatchQueue.main)
            .assign(to: &$bufferedTime)
        
        playerItem.publisher(for: \.isPlaybackLikelyToKeepUp)
            .receive(on: DispatchQueue.main)
            .assign(to: &$isPlaybackLikelyToKeepUp)
        
        // CombineLatest로 버퍼링 및 재생 조건 확인
        Publishers.CombineLatest($bufferedTime, $isPlaybackLikelyToKeepUp)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] bufferedTime, isLikelyToKeepUp in
                self?.checkPlaybackStart(bufferedTime: bufferedTime, isLikelyToKeepUp: isLikelyToKeepUp)
            }
            .store(in: &cancellables)
        
        // Combine으로 재생 완료 이벤트 감지
        NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: playerItem)
            .sink { [weak self] _ in
                DispatchQueue.main.async {
                    self?.handlePlaybackEnded()
                }
            }
            .store(in: &cancellables)
        
        self.fetchAlbumArtAndUpdateNowPlayingInfo()
    }
    
    private func checkPlaybackStart(bufferedTime: Double, isLikelyToKeepUp: Bool) {
        if bufferedTime >= minimumBufferedTime && isLikelyToKeepUp {
            if !isPlaying {
                player?.play()
                isPlaying = true
                isLoading = false
                DEBUG_LOG("재생 시작: \(bufferedTime)초 버퍼링")
            }
        } else {
            if isPlaying {
                player?.pause()
                isPlaying = false
                isLoading = true
                DEBUG_LOG("재생 중단: 버퍼링 부족 (\(bufferedTime)초)")
            }
        }
        updateNowPlayingInfo()
    }
    
    private func handlePlaybackEnded() {
        if let hasNextContent = self.playlistManager?.hasNextContent(), hasNextContent {
            self.playNextContent()
        } else {
            self.stop()
        }
    }
    
    func playNextContent() {
        stop()
        stopLooping()
        loopState = .idle
        
        if let content = playlistManager?.moveToNext() {
            generateUrl(contentId: content.id) { [unowned self] url in
                self.urlGenerateSuccess(content: content, url: url)
            } onFailure: {
                self.playNextContent()
            }
        }
    }
    
    func playPreviousContent() {
        stop()
        stopLooping()
        loopState = .idle
        
        if let content = playlistManager?.moveToPrevious() {
            generateUrl(contentId: content.id) { [unowned self] url in
                self.urlGenerateSuccess(content: content, url: url)
            } onFailure: {
                self.playPreviousContent()
            }
        }
    }
    
    func playSelectedContent(contentId: Int) {
        if let content = playlistManager?.findByContentId(contentId: contentId) {
            generateUrl(contentId: content.id) { [unowned self] url in
                self.urlGenerateSuccess(content: content, url: url)
            } onFailure: {
                self.playPreviousContent()
            }
        }
    }
    
    private func urlGenerateSuccess(content: AudioContentPlaylistContent, url: String) {
        id = content.id
        title = content.title
        nickname = content.creatorNickname
        coverImageUrl = content.coverUrl
        creatorProfileUrl = content.creatorProfileUrl
        setupPlayer(with: URL(string: url)!)
    }
    
    func playOrPause() {
        if player?.timeControlStatus == .paused && !isPlaying {
            player?.play()
            isPlaying = true
        } else {
            player?.pause()
            isPlaying = false
        }
    }
    
    private func stop() {
        isPlaying = false
        isLoading = false
        player?.pause()
        player?.seek(to: .zero)
        currentTime = 0
    }
    
    func resetPlayer() {
        stop()
        stopLooping()
        loopState = .idle
        duration = 0
        isShowingMiniPlayer = false
        
        title = ""
        nickname = ""
        coverImageUrl = ""
        creatorProfileUrl = ""
        
        cancellables.removeAll()
        playlistManager = nil
        unRegisterRemoteControlEvents()
    }
    
    func seek(to time: Double) {
        stopLooping()
        loopState = .idle
        let cmTime = CMTime(seconds: time, preferredTimescale: 600)
        player?.seek(to: cmTime)
    }
    
    func seekBackward(seconds: Double) {
        guard let currentTime = player?.currentTime() else { return }
        let newTimeInSeconds = CMTimeGetSeconds(currentTime) - seconds
        seek(to: max(newTimeInSeconds, 0))
    }
    
    func seekForward(seconds: Double) {
        guard let currentTime = player?.currentTime(), let duration = player?.currentItem?.duration else { return }
        let newTimeInSeconds = CMTimeGetSeconds(currentTime) + seconds
        let durationInSeconds = CMTimeGetSeconds(duration)
        seek(to: min(newTimeInSeconds, durationInSeconds))
    }
    
    func toggleLoop() {
        guard let currentTime = player?.currentTime() else { return }
        
        switch loopState {
        case .idle:
            loopState = .waitingForEnd(start: currentTime)
            
        case .waitingForEnd(let start):
            let end = currentTime
            loopState = .looping(start: start, end: end)
            startLooping(from: start, to: end)
            
        case .looping:
            stopLooping()
            loopState = .idle
        }
    }
    
    private func startLooping(from start: CMTime, to end: CMTime) {
        let interval = CMTime(seconds: 0.1, preferredTimescale: 600)
        
        timeObserver = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] currentTime in
            if currentTime >= end {
                self?.player?.seek(to: start, toleranceBefore: .zero, toleranceAfter: .zero)
            }
        }
    }
    
    private func stopLooping() {
        if let observer = timeObserver {
            player?.removeTimeObserver(observer)
            timeObserver = nil
        }
    }
    
    private func generateUrl(contentId: Int, onSuccess: @escaping (String) -> Void, onFailure: @escaping () -> Void) {
        if contentId < 0 {
            onFailure()
        }
        
        if !isLoading {
            isLoading = true
            
            repository.generateUrl(contentId: contentId)
                .sink { result in
                    switch result {
                    case .finished:
                        DEBUG_LOG("finish")
                    case .failure(let error):
                        ERROR_LOG(error.localizedDescription)
                    }
                } receiveValue: { [unowned self] response in
                    self.isLoading = false
                    let responseData = response.data
                    
                    do {
                        let jsonDecoder = JSONDecoder()
                        let decoded = try jsonDecoder.decode(ApiResponse<GenerateUrlResponse>.self, from: responseData)
                        
                        if let data = decoded.data, !data.contentUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, decoded.success {
                            onSuccess(data.contentUrl)
                        } else {
                            self.isLoading = false
                            onFailure()
                        }
                    } catch {
                        self.isLoading = false
                        onFailure()
                    }
                }
                .store(in: &cancellables)
        }
    }
    
    private func fetchAlbumArtAndUpdateNowPlayingInfo() {
        guard let url = URL(string: coverImageUrl) else {
            print("잘못된 이미지 URL")
            registerNowPlayingInfoCenter(with: nil) // 앨범 아트 없이 업데이트
            return
        }
        
        let processor = DownsamplingImageProcessor(size: CGSize(width: 240, height: 240))
        
        KingfisherManager.shared.retrieveImage(
            with: url,
            options: [
                .processor(processor),
                .scaleFactor(UIScreen.main.scale),
                .cacheOriginalImage
            ]
        ) { [weak self] result in
            switch result {
            case .success(let value):
                self?.registerNowPlayingInfoCenter(with: value.image)
            case .failure(_):
                self?.registerNowPlayingInfoCenter(with: nil)
            }
        }
    }
    
    private func updateNowPlayingInfo() {
        guard var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo else { return }

        nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime().seconds
        nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0

        let duration = CMTimeGetSeconds(player?.currentItem?.duration ?? CMTime.zero)
        if duration.isFinite {
            nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
        }

        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
    
    private func registerNowPlayingInfoCenter(with albumArtImage: UIImage?) {
        guard let currentItem = player?.currentItem else { return }
        
        var nowPlayingInfo = [String: Any]()
        nowPlayingInfo[MPMediaItemPropertyTitle] = title
        nowPlayingInfo[MPMediaItemPropertyArtist] = nickname
        
        if let image = albumArtImage {
            let artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
            nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
        }
        
        if let player = player {
            nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
            nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime().seconds
            
            // CMTimeGetSeconds를 사용해 duration을 가져옴
            let duration = CMTimeGetSeconds(currentItem.duration)
            if duration.isFinite {
                nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
            }
        }
        
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
    
    private func registerRemoteControlEvents() {
        let center = MPRemoteCommandCenter.shared()
        
        center.playCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            self.playOrPause()
            return .success
        }
        
        center.pauseCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            self.playOrPause()
            return .success
        }
        
        center.nextTrackCommand.isEnabled = true
        center.nextTrackCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            self.playNextContent()
            return .success
        }
        
        center.previousTrackCommand.isEnabled = true
        center.previousTrackCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            self.playPreviousContent()
            return .success
        }
    }
    
    private func unRegisterRemoteControlEvents() {
        let center = MPRemoteCommandCenter.shared()
        center.playCommand.removeTarget(nil)
        center.pauseCommand.removeTarget(nil)
        center.nextTrackCommand.removeTarget(nil)
        center.previousTrackCommand.removeTarget(nil)
        UIApplication.shared.endReceivingRemoteControlEvents()
    }
}

extension AVPlayer {
    /// AVPlayer의 addPeriodicTimeObserver를 Combine Publisher로 변환
    func periodicTimePublisher(interval: CMTime, queue: DispatchQueue = .main) -> AnyPublisher<CMTime, Never> {
        let subject = PassthroughSubject<CMTime, Never>()
        
        // Periodic Time Observer 추가
        let timeObserverToken = self.addPeriodicTimeObserver(forInterval: interval, queue: queue) { time in
            subject.send(time) // 현재 시간을 스트림으로 보냄
        }
        
        // Subscription이 끝나면 Observer 제거
        return subject
            .handleEvents(receiveCancel: { [weak self] in
                self?.removeTimeObserver(timeObserverToken)
            })
            .eraseToAnyPublisher()
    }
}
