//
//  ContentPlayManager.swift
//  SodaLive
//
//  Created by klaus on 2023/08/11.
//

import Foundation
import AVKit
import MediaPlayer
import ObjectBox

final class ContentPlayManager: NSObject, ObservableObject {
    static let shared = ContentPlayManager()
    
    var creatorId = 0
    @Published var contentId: Int = 0
    
    @Published private (set) var duration: TimeInterval = 0
    
    @Published var title = ""
    @Published var nickname = ""
    @Published var coverImage = ""
    
    @Published var isFree: Bool? = nil
    @Published var isPreview: Bool? = nil
    @Published private (set) var isShowingMiniPlayer = false
    @Published private (set) var isPlaying = false
    
    @Published var isLoading = false
    @Published var errorMessage = ""
    @Published var isShowPopup = false
    
    var player: AVAudioPlayer!
    
    var startTimer: (() -> Void)?
    var stopTimer: (() -> Void)?
    
    private var playbackTrackingId: Id = 0
    private let repository = PlaybackTrackingRepository()
}

extension ContentPlayManager {
    func playAudio(
        creatorId: Int = 0,
        contentId: Int = 0,
        title: String = "",
        nickname: String = "",
        coverImage: String = "",
        contentUrl: String = "",
        isFree: Bool? = nil,
        isPreview: Bool? = nil
    ) {
        if contentId <= 0 {
            return
        }
        
        if let startTimer = startTimer {
            startTimer()
        }
        
        if self.contentId > 0 && self.contentId == contentId {
            player?.play()
            isPlaying = player.isPlaying
        } else {
            isLoading = true
            stopAudio()
            
            self.creatorId = creatorId
            self.contentId = contentId
            self.title = title
            self.nickname = nickname
            self.coverImage = coverImage
            self.isFree = isFree
            self.isPreview = isPreview
            
            guard let url = URL(string: contentUrl) else {
                showError()
                return
            }
            
            URLSession.shared.dataTask(with: url) { [unowned self] data, response, error in
                guard let audioData = data else {
                    self.isLoading = false
                    return
                }
                
                do {
                    let audioSession = AVAudioSession.sharedInstance()
                    try audioSession.setCategory(.playback, mode: .moviePlayback)
                    try audioSession.setActive(true)
                    
                    self.player = try AVAudioPlayer(data: audioData)
                    saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: 0)
                    
                    DispatchQueue.main.async {
                        self.player?.volume = 1
                        self.player?.delegate = self
                        self.player?.prepareToPlay()
                        
                        self.duration = self.player.duration
                        self.player?.play()
                        self.isPlaying = self.player.isPlaying
                        self.isShowingMiniPlayer = true
                        UIApplication.shared.beginReceivingRemoteControlEvents()
                    }
                    
                    self.registerNowPlayingInfoCenter()
                    self.registerRemoteControlEvents()
                } catch {
                    DispatchQueue.main.async {
                        self.showError()
                    }
                }
                
                DispatchQueue.main.async {
                    self.isLoading = false
                }
            }.resume()
        }
    }
    
    func stopAudio() {
        if let player = player {
            player.stop()
            setEndPositionPlaybackTracking(progress: Int(player.currentTime))
            
            player.currentTime = 0
            isPlaying = player.isPlaying
        }
        
        resetAudioData()
        unRegisterRemoteControlEvents()
    }
    
    func conditionalStopAudio(contentId: Int) {
        if self.contentId == contentId {
            stopAudio()
        }
    }
    
    func pauseAudio() {
        if let player = player {
            player.pause()
            isPlaying = player.isPlaying
            if let stopTimer = stopTimer {
                stopTimer()
            }
        }
    }
    
    func resetAudioData() {
        title = ""
        nickname = ""
        coverImage = ""
        contentId = 0
        duration = 0
        
        isPreview = false
        isShowingMiniPlayer = false
        player = nil
        startTimer = nil
        stopTimer = nil
    }
    
    func setCurrentTime(_ progress: TimeInterval) {
        if let player = player, contentId > 0 {
            player.currentTime = progress
            saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: Int(progress))
        }
    }
    
    private func repeatAudio() {
        if let stopTimer = stopTimer {
            stopTimer()
        }
        
        player.stop()
        setEndPositionPlaybackTracking(progress: Int(player.currentTime))
        player.currentTime = 0
        
        saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: 0)
        player.play()
        
        if let startTimer = startTimer {
            startTimer()
        }
    }
    
    private func showError() {
        self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
        self.isShowPopup = true
        self.resetAudioData()
    }
    
    private func registerNowPlayingInfoCenter() {
        let center = MPNowPlayingInfoCenter.default()
        var nowPlayingInfo = center.nowPlayingInfo ?? [String: Any]()
        
        nowPlayingInfo[MPMediaItemPropertyTitle] = title
        nowPlayingInfo[MPMediaItemPropertyArtist] = nickname
        if let artworkURL = URL(string: coverImage), let imageData = try? Data(contentsOf: artworkURL), let artworkImage = UIImage(data: imageData) {
            let artwork = MPMediaItemArtwork(boundsSize: artworkImage.size) { size in
                return artworkImage
            }
            nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
        }
        
        // 콘텐츠 총 길이
        nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player.duration
        // 콘텐츠 재생 시간에 따른 progressBar 초기화
        nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
        // 콘텐츠 현재 재생시간
        nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
        
        center.nowPlayingInfo = nowPlayingInfo
    }
    
    private func registerRemoteControlEvents() {
        let center = MPRemoteCommandCenter.shared()
        
        center.playCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            if let player = player {
                player.play()
                self.isPlaying = player.isPlaying
                if let startTimer = self.startTimer {
                    startTimer()
                }
            }
            
            return .success
        }
        
        center.pauseCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
            self.pauseAudio()
            return .success
        }
    }
    
    private func unRegisterRemoteControlEvents() {
        let center = MPRemoteCommandCenter.shared()
        center.playCommand.removeTarget(nil)
        center.pauseCommand.removeTarget(nil)
        UIApplication.shared.endReceivingRemoteControlEvents()
    }
}

extension ContentPlayManager {
    private func saveNewPlaybackTracking(totalDuration: Int, progress: Int) {
        if creatorId != UserDefaults.int(forKey: .userId) {
            playbackTrackingId = repository
                .savePlaybackTracking(data: PlaybackTracking(
                    audioContentId: contentId,
                    totalDuration: totalDuration,
                    startPosition: progress,
                    isFree: isFree ?? true,
                    isPreview: isPreview ?? true)
                )
        }
    }
    
    private func setEndPositionPlaybackTracking(progress: Int) {
        if creatorId != UserDefaults.int(forKey: .userId) && playbackTrackingId > 0 {
            if let playbackTracking = repository.getPlaybackTracking(id: playbackTrackingId) {
                playbackTracking.endPosition = progress
                _ = repository.savePlaybackTracking(data: playbackTracking)
            }
            
            playbackTrackingId = 0
        }
    }
}

extension ContentPlayManager: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        if UserDefaults.bool(forKey: .isContentPlayLoop) {
            repeatAudio()
        } else {
            stopAudio()
        }
    }
}