//
//  AppEnvironment.swift
//  LeCountdown
//
//  Created by Laurent Morvillier on 31/01/2023.
//

import Foundation
import BackgroundTasks
import SwiftUI
import Intents
import AudioToolbox
import ActivityKit
import AVFoundation

enum BGTaskIdentifier : String {
    case refresh = "com.staxriver.lecountdown.refresh"
}

fileprivate enum Const: String {
    case confirmationSound = "PVP_Stab_Oneshot_Bleep_Em.wav"
}

class Conductor: ObservableObject {
    
    static let maestro: Conductor = Conductor()

    @Published var soundPlayer: SoundPlayer? = nil
    
    fileprivate var _delayedSoundPlayers: [TimerID : DelaySoundPlayer] = [:]
    
    @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval]
    @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date]

    @Published private (set) var liveTimers: [LiveTimer] = []
    
    init() {
        self.currentCountdowns = Conductor.savedCountdowns
        self.currentStopwatches = Conductor.savedStopwatches
    }
    
    @Published var cancelledCountdowns: [String] = []
    
    @Published var currentCountdowns: [String : DateInterval] = [:] {
        didSet {
            Conductor.savedCountdowns = currentCountdowns
//            Logger.log("**** currentCountdowns didSet, count = \(currentCountdowns.count)")
            withAnimation {
                self._buildLiveTimers()
            }
        }
    }
    
    @Published var currentStopwatches: [String : Date] = [:] {
        didSet {
            Conductor.savedStopwatches = currentStopwatches
            withAnimation {
                self._buildLiveTimers()
            }
        }
    }
    
    func removeLiveTimer(id: String) {
        
        Logger.log("removeLiveTimer")
        self.liveTimers.removeAll(where: { $0.id == id })
        self.cancelledCountdowns.removeAll(where: { $0 == id })
        if let soundPlayer = self._delayedSoundPlayers[id] {
            soundPlayer.stop()
        }
    }
    
    fileprivate func _buildLiveTimers() {
        
        Logger.log("_buildLiveTimers")
        
        let liveCountdowns = self.currentCountdowns.map {
            return LiveTimer(id: $0, date: $1.end)
        }
        
        // add countdown if not present
        for liveCountdown in liveCountdowns {
            if let index = self.liveTimers.firstIndex(where: { $0.id == liveCountdown.id }) {
                self.liveTimers.remove(at: index)
                self.liveTimers.insert(liveCountdown, at: index)
            } else {
                self.liveTimers.append(liveCountdown)
            }
        }
            
        let liveStopwatches = self.currentStopwatches.map {
            return LiveTimer(id: $0, date: $1)
        }
        for liveStopwatch in liveStopwatches {
            if let index = self.liveTimers.firstIndex(where: { $0.id == liveStopwatch.id }) {
                self.liveTimers.remove(at: index)
                self.liveTimers.insert(liveStopwatch, at: index)
            } else {
                self.liveTimers.append(liveStopwatch)
            }
        }
        
    }
    
    func notifyUser(countdownId: String) {
//        self._playSound(timerId: countdownId)
        self._endCountdown(countdownId: countdownId, cancel: false)
    }
    
    fileprivate func _recordActivity(countdownId: String) {
        let context = PersistenceController.shared.container.viewContext
        if let countdown = context.object(stringId: countdownId) as? Countdown,
            let dateInterval = self.currentCountdowns[countdownId] {
            do {
                try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval)
            } catch {
                Logger.error(error)
                // TODO: show error to user
            }
        }
    }
    
    // MARK: - Countdown
            
    func startCountdown(_ date: Date, countdown: Countdown) {
//        DispatchQueue.main.async {
            
            Logger.log("Starts countdown: \(countdown.displayName)")
            
            // cleanup existing countdowns
            self.removeLiveTimer(id: countdown.stringId)
            
//            self._cleanupTimers.removeValue(forKey: countdown.stringId)
            
            let dateInterval = DateInterval(start: Date(), end: date)
            self.currentCountdowns[countdown.stringId] = dateInterval
            
//            self._launchLiveActivity(countdown: countdown, endDate: date)
            
//            self._createTimerIntent(countdown)
            
//            Logger.log("countdowns count = \(self.currentCountdowns.count)")

//        }
    }
    
    func cancelCountdown(id: TimerID) {
        CountdownScheduler.master.cancelCurrentNotifications(countdownId: id)
        self.cancelSoundPlayer(id: id)
        self.cancelledCountdowns.append(id)
        self._endCountdown(countdownId: id, cancel: true)
    }
    
    fileprivate func _endCountdown(countdownId: String, cancel: Bool) {
        DispatchQueue.main.async {
            if !cancel {
                self._recordActivity(countdownId: countdownId)
            }
            self.currentCountdowns.removeValue(forKey: countdownId)
        }
    }
    
    func prepareAlarm(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) {
        
        DispatchQueue.main.async {
            
            do {
                let date = Date(timeIntervalSinceNow: countdown.duration)

                self.startCountdown(date, countdown: countdown)

                let soundFile = try SoundFile(fullName: countdown.soundName)
                let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile)
                self._delayedSoundPlayers[countdown.stringId] = soundPlayer
                try soundPlayer.start(in: countdown.duration,
                                                 repeatCount: Int(countdown.repeatCount))
                
                if Preferences.playConfirmationSound {
                    self._playSound(Const.confirmationSound.rawValue)
                }
                
                handler(.success(date))
            } catch {
                Logger.error(error)
                handler(.failure(error))
            }
        }
    }
    
    // MARK: - Stopwatch
    
    func startStopwatch(_ stopwatch: Stopwatch) {
        DispatchQueue.main.async {
            let now = Date()
            Conductor.maestro.currentStopwatches[stopwatch.stringId] = now
            
            if Preferences.playConfirmationSound {
                self._playSound(Const.confirmationSound.rawValue)
            }
            
            self._launchLiveActivity(stopwatch: stopwatch, start: now)
            
//            self._createTimerIntent(stopwatch)
            
        }
    }
    
    func stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) {
        if let start = Conductor.maestro.currentStopwatches[stopwatch.stringId] {
            Conductor.maestro.currentStopwatches.removeValue(forKey: stopwatch.stringId)
            do {
                try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: start, end: end ?? Date()))
            } catch {
                Logger.error(error)
            }
            self._endLiveActivity(timerId: stopwatch.stringId)
        }
    }
    
    func restore() {
        
        for (countdownId, interval) in self.currentCountdowns {
            if !self._delayedSoundPlayers.contains(where: { $0.key == countdownId }) {
                
                let context = PersistenceController.shared.container.viewContext
                if let countdown = context.object(stringId: countdownId) as? Countdown {
                    
                    do {
                        let soundFile = try SoundFile(fullName: countdown.soundName)
                        let soundPlayer = try DelaySoundPlayer(timerID: countdownId, soundFile: soundFile)
                        self._delayedSoundPlayers[countdown.stringId] = soundPlayer
                        try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount))
                    } catch {
                        Logger.error(error)
                    }
                }
            }
        }
        
    }
    
    // MARK: - Cleanup
    
    func cleanup() {
        self._cleanupCountdowns()
        self._buildLiveTimers()
    }
    
    fileprivate func _cleanupCountdowns() {
        let now = Date()
        for (key, value) in self.currentCountdowns {
            if value.end < now || self.cancelledCountdowns.contains(key) {
                self._endCountdown(countdownId: key, cancel: false)
            }
        }
    }
    
    // MARK: - Sound
    
    fileprivate func _playSound(timerId: String) {
        
        let context = PersistenceController.shared.container.viewContext
        
        var coolSound: Sound? = nil
        let timer = context.object(stringId: timerId)
        switch timer {
        case let cd as Countdown:
            coolSound = cd.someSound
        case let sw as Stopwatch:
            coolSound = sw.coolSound
        default:
            break
        }

        if let coolSound {
            self.playSound(coolSound)
        } else {
            print("No sound to play!")
        }
        AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
    }

    func playSound(_ sound: Sound) {
        self._playSound(sound.fileName)
    }
    
    func playSound(_ sound: Sound, duration: TimeInterval) {
        self._playSound(sound.fileName, duration: duration)
    }
    
    fileprivate func _playSound(_ filename: String, duration: TimeInterval? = nil) {
        do {
            let soundFile = try SoundFile(fullName: filename)
            let soundPlayer = SoundPlayer()
            self.soundPlayer = soundPlayer
            if let duration {
                try soundPlayer.play(soundFile: soundFile, for: duration)
            } else {
                try soundPlayer.playSound(soundFile: soundFile)
            }
        } catch {
            Logger.error(error)
            // TODO: manage error
        }
    }
    
    func cancelSoundPlayer(id: TimerID) {
        if let soundPlayer = self._delayedSoundPlayers[id] {
            soundPlayer.stop()
            self._delayedSoundPlayers.removeValue(forKey: id)
        }
        
        self.deactivateAudioSessionIfPossible()
    }
    
    func deactivateAudioSessionIfPossible() {
        if self._delayedSoundPlayers.isEmpty {
            do {
                try AVAudioSession.sharedInstance().setActive(false)
            } catch {
                Logger.error(error)
            }
        }
    }
    
    func stopMainPlayersIfPossible() {
        self.soundPlayer?.stop()
        self.soundPlayer = nil
    }
    
    // MARK: - Intent

//    fileprivate func _createTimerIntent(_ timer: AbstractTimer) {
//        let intent = LaunchTimerIntent()
//
//        let invocationPhrase = String(format: NSLocalizedString("Launch %@", comment: ""), timer.displayName)
//        intent.suggestedInvocationPhrase = String(format: invocationPhrase, timer.displayName)
//        intent.timer = TimerIdentifier(identifier: timer.stringId, display: timer.displayName)
//
//        let interaction = INInteraction(intent: intent, response: nil)
//        interaction.donate()
//    }

    fileprivate func _scheduleAppRefresh(countdown: Countdown) {
        let request = BGAppRefreshTaskRequest(identifier: BGTaskIdentifier.refresh.rawValue)
        request.earliestBeginDate = Date(timeIntervalSinceNow: countdown.duration)
        do {
            try BGTaskScheduler.shared.submit(request)
            print("request submitted with date: \(String(describing: request.earliestBeginDate))")
        } catch {
            Logger.error(error)
        }
    }

    // MARK: - Live Activity
    
    fileprivate func _launchLiveActivity(stopwatch: Stopwatch, start: Date) {
        
        if #available(iOS 16.2, *) {
            
            if ActivityAuthorizationInfo().areActivitiesEnabled {
                
                let contentState = LaunchWidgetAttributes.ContentState(ended: false)
                let attributes = LaunchWidgetAttributes(id: stopwatch.stringId, name: stopwatch.displayName, date: start)
                
                let activityContent = ActivityContent(state: contentState, staleDate: nil)
                do {
                    let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent)
                    print("Requested a Live Activity: \(String(describing: liveActivity.id)).")
                } catch (let error) {
                    Logger.error(error)
                }
                
            }
        } else {
            // Fallback on earlier versions
        }

    }

    fileprivate func _liveActivity(timerId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? {
        return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == timerId } )
    }
    
    func updateLiveActivities() {
        print("update live activity...")
        
        for (countdownId, interval) in self.currentCountdowns {
            
            if interval.end < Date() {
                self._endLiveActivity(timerId: countdownId)
            }
            
//            if let activity = self._liveActivity(countdownId: countdownId) {
//
//                Task {
//
//                    if ended {
//                        self._endLiveActivity(countdownId: countdownId)
//                    }
//
////                    let state = LaunchWidgetAttributes.ContentState(ended: ended)
////                    let content = ActivityContent(state: state, staleDate: interval.end)
////                    await activity.update(content)
////                    print("Ending the Live Activity: \(activity.id)")
//                }
//            }
        }
        
    }
    
    fileprivate func _endLiveActivity(timerId: String) {
        if #available(iOS 16.2, *) {
            print("Try to end the Live Activity: \(timerId)")
            if let activity = self._liveActivity(timerId: timerId) {
                Task {
                    let state = LaunchWidgetAttributes.ContentState(ended: true)
                    let content = ActivityContent(state: state, staleDate: Date())
                    await activity.end(content, dismissalPolicy: .immediate)
                    print("Ending the Live Activity: \(activity.id)")
                }
            }
        }
    }

}
