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

import Foundation
import ActivityKit
import BackgroundTasks

enum Key : String {
    case countdowns
    case stopwatches
}

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

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

    @Published var liveTimers: [LiveTimer] = []
    
    init() {
        self.currentCountdowns = Conductor.savedCountdowns
        self.currentStopwatches = Conductor.savedStopwatches
    }
    
    @Published var currentCountdowns: [String : DateInterval] = [:] {
        didSet {
            Conductor.savedCountdowns = currentCountdowns
            self._buildLiveTimers()
        }
    }
    
    @Published var currentStopwatches: [String : Date] = [:] {
        didSet {
            Conductor.savedStopwatches = currentStopwatches
            self._buildLiveTimers()
        }
    }
    
    fileprivate func _buildLiveTimers() {
        var countdowns = self.currentCountdowns.map {
            return LiveTimer(id: $0, date: $1.end)
        }
        let stopwatches = self.currentStopwatches.map {
            return LiveTimer(id: $0, date: $1)
        }
        countdowns.append(contentsOf: stopwatches)
        self.liveTimers = countdowns.sorted()
    }
    
    func refreshHack(_ liveTimer: LiveTimer) {
        
        
    }
        
    func startCountdown(_ date: Date, countdown: Countdown) {
        DispatchQueue.main.async {
            let dateInterval = DateInterval(start: Date(), end: date)
            self.currentCountdowns[countdown.stringId] = dateInterval
                        
            self._launchLiveActivity(countdown: countdown, endDate: date)
        }
    }

    func notifyUser(countdownId: String) {
        self._playSound(countdownId: countdownId)
        self._endCountdown(countdownId: countdownId, cancel: false)
    }
    
    func cancelCountdown(id: String) {
        CountdownScheduler.master.cancelCurrentNotifications(countdownId: id)
        self._endCountdown(countdownId: id, cancel: true)
    }
    
    fileprivate func _endCountdown(countdownId: String, cancel: Bool) {
        DispatchQueue.main.async {
            if !cancel {
                self._recordActivity(countdownId: countdownId)
            }
            
            if self.currentCountdowns.removeValue(forKey: countdownId) != nil {
                self._endLiveActivity(countdownId: countdownId)
            }
        }
    }
    
    func cleanup() {
        let now = Date()
        for (key, value) in self.currentCountdowns {
            if value.end < now {
                self._endCountdown(countdownId: key, 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 {
                print("Could not record activity = \(error)")
                // TODO: show error to user
            }
        }
    }
    
    // MARK: - Sound
    
    fileprivate func _playSound(countdownId: String) {
        
        let countdown = PersistenceController.shared.container.viewContext.object(stringId: countdownId) as? Countdown
        
        let coolSound = countdown?.coolSound ?? Sound.allCases[0]
        
        do {
            let soundFile = try coolSound.soundFile()
            let soundPlayer = SoundPlayer()
            self.soundPlayer = soundPlayer
            try soundPlayer.playSound(soundFile: soundFile, repeats: countdown?.repeats ?? true)
        } catch {
            print("error = \(error)")
            // TODO: manage error
        }
         
    }

    func stopSoundIfPossible() {
        self.soundPlayer?.stop()
        self.soundPlayer = nil
    }
    
    // MARK: - Live Activity
    
    fileprivate func _launchLiveActivity(countdown: Countdown, endDate: Date) {
        
        if ActivityAuthorizationInfo().areActivitiesEnabled {
            
            let contentState = LaunchWidgetAttributes.ContentState(ended: false)
            let attributes = LaunchWidgetAttributes(id: countdown.stringId, name: countdown.displayName, endDate: endDate)
            let activityContent = ActivityContent(state: contentState, staleDate: endDate.addingTimeInterval(30.0))

            do {
                let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent)
                print("Requested a Countdown Live Activity: \(String(describing: liveActivity.id)).")
            } catch (let error) {
                print("Error requesting countdown Live Activity \(error.localizedDescription).")
            }

            self._scheduleAppRefresh(countdown: countdown)
            
        }
    }
    
    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 {
            print("Could not schedule app refresh: \(error)")
        }
    }
    
    fileprivate func _liveActivity(countdownId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? {
        return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == countdownId } )
    }
    
    func updateLiveActivities() {
        print("update live activity...")
        
        for (countdownId, interval) in self.currentCountdowns {
            
            if interval.end < Date() {
                self._endLiveActivity(countdownId: 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(countdownId: String) {
        
        print("Try to end the Live Activity: \(countdownId)")

        if let activity = self._liveActivity(countdownId: countdownId) {
            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)")
            }
        }
    }
    
}
