//
//  TimerModule.swift
//  Werkout_ios
//
//  Created by Trey Tartt on 6/14/23.
//

import Foundation
import WatchConnectivity
import AVFoundation
import HealthKit

enum WatchActions: Codable {
    case nextExercise
    case restartExercise
    case previousExercise
    case stopWorkout
    case pauseWorkout
    case workoutComplete(Data)
}

enum PhoneToWatchActions: Codable {
    case inExercise(WatchPackageModel)
    case reset
    case endWorkout
}

class BridgeModule: NSObject, ObservableObject {
    private let kMessageKey = "message"

    static let shared = BridgeModule()
    @Published var isShowingOnExternalDisplay = false

    @Published var isInWorkout = false
    var completedWorkout: (() -> Void)?
    @Published var currentWorkoutRunTimeInSeconds: Int = -1
    private var currentWorkoutRunTimer: Timer?
    
    public private(set) var workoutStartDate: Date?

    private var currentExerciseTimer: Timer?
    
    @Published public private(set) var currentExerciseInfo = CurrentWorkoutInfo()
    @Published var previewWorkout: Workout?
    
    @Published var currentExerciseTimeLeft: Int = 0
    var currentExercisePositionString: String?
    
    private var isWatchConnected = false
    // workoutEndDate fills out WatchPackageModel.workoutEndDate which
    // tells the watch app to stop the workout
    public private(set) var workoutEndDate: Date?
    @Published public private(set) var healthKitUUID: UUID?

    var audioPlayer: AVAudioPlayer?
    var avPlayer: AVPlayer?
    
    func start(workout: Workout) {
        currentExerciseInfo.complete = {
            self.completeWorkout()
        }
        
        currentExerciseInfo.start(workout: workout)
        currentWorkoutRunTimeInSeconds = 0
        currentWorkoutRunTimer?.invalidate()
        currentWorkoutRunTimer = nil
        
        if let superetExercise = currentExerciseInfo.currentExercise {
            updateCurrent(exercise: superetExercise)
            startWorkoutTimer()
            workoutStartDate = Date()
            isInWorkout = true
            
            if WCSession.isSupported() {
                WCSession.default.delegate = self
                WCSession.default.activate()
            }
        }
    }
    
    func goToExerciseAt(section: Int, row: Int) {
        if let superetExercise = currentExerciseInfo.goToWorkoutAt(supersetIndex: section,
                                                                       exerciseIndex: row) {
            updateCurrent(exercise: superetExercise)
        }
    }
    
    var nextExerciseObject: SupersetExercise? {
        currentExerciseInfo.nextExercise
    }
    
    func resetCurrentWorkout() {
        DispatchQueue.main.async {
            self.currentWorkoutRunTimeInSeconds = 0
            self.currentWorkoutRunTimer?.invalidate()
            self.currentWorkoutRunTimer = nil
            
            self.currentExerciseTimer?.invalidate()
            self.currentExerciseTimer = nil
            
            self.currentWorkoutRunTimeInSeconds = -1
            self.currentExerciseInfo.reset()
    
            self.isInWorkout = false
            self.workoutStartDate = nil
            self.workoutEndDate = nil
        }
    }
    
    private func startWorkoutTimer() {
        currentWorkoutRunTimer?.invalidate()
        currentWorkoutRunTimer = nil
        currentWorkoutRunTimer = Timer.scheduledTimer(timeInterval: 1,
                                     target: self,
                                     selector: #selector(addOneToWorkoutRunTime),
                                     userInfo: nil,
                                     repeats: true)
        currentWorkoutRunTimer?.fire()
    }
    
    private func startExerciseTimerWith(duration: Int) {
        DispatchQueue.main.async {
            self.currentExerciseTimer?.invalidate()
            self.currentExerciseTimer = nil
            self.currentExerciseTimeLeft = duration
            self.currentExerciseTimer = Timer.scheduledTimer(timeInterval: 1,
                                         target: self,
                                              selector: #selector(self.updateCurrentExerciseTimer),
                                         userInfo: nil,
                                         repeats: true)
            self.currentExerciseTimer?.fire()
        }
    }
    
    @objc func updateCurrentExerciseTimer() {
        if currentExerciseTimeLeft > 1 {
            currentExerciseTimeLeft -= 1
                    
            if let currentExercise = currentExerciseInfo.allSupersetExecercise, let audioQueues = currentExercise.audioQueues {
                if let audioQueue = audioQueues.first(where: {
                    $0.playAt == currentExerciseTimeLeft
                }) {
                    switch audioQueue.audioType {
                        
                    case .shortBeep:
                        playBeep()
                    case .finishBeep:
                        playFinished()
                    case .remoteURL(let url):
                        playRemoteAudio(fromURL: url)
                    }
                }
            }
            sendCurrentExerciseToWatch()
        } else {
            nextExercise()
        }
    }
    
    func pauseWorkout() {
        if let _ = currentExerciseTimer {
            currentExerciseTimer?.invalidate()
            currentExerciseTimer = nil
        } else {
            startExerciseTimerWith(duration: currentExerciseTimeLeft)
        }
    }
    
    func nextExercise() {
        if let nextSupersetExercise = currentExerciseInfo.nextExercise {
            updateCurrent(exercise: nextSupersetExercise)
        } else {
            completeWorkout()
        }
    }
    
    func previousExercise() {
        if let nextSupersetExercise = currentExerciseInfo.previousExercise {
            updateCurrent(exercise: nextSupersetExercise)
        } else {
            completeWorkout()
        }
    }
    
    func restartExercise() {
        if let currentExercise = currentExerciseInfo.currentExercise {
            updateCurrent(exercise: currentExercise)
        }
    }
    
    @objc func addOneToWorkoutRunTime() {
        currentWorkoutRunTimeInSeconds += 1
    }
    
    func updateCurrent(exercise: SupersetExercise) {
        DispatchQueue.main.async {
            self.currentExerciseTimer?.invalidate()
            self.currentExerciseTimer = nil
                    
            if let duration = exercise.duration, duration > 0 {
                self.startExerciseTimerWith(duration: duration)
            }
            self.sendCurrentExerciseToWatch()
        }
    }
    
    func completeWorkout() {
        self.currentExerciseTimer?.invalidate()
        self.currentExerciseTimer = nil
        self.isInWorkout = false
        
        workoutEndDate = Date()
        if let completedWorkout = completedWorkout {
            completedWorkout()
            self.completedWorkout = nil
        }
    }
    
    func playRemoteAudio(fromURL url: URL) {
#if os(iOS)
        let playerItem = AVPlayerItem(url: url)
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback,
                                                            mode: .default,
                                                            options: [.mixWithOthers])
            try AVAudioSession.sharedInstance().setActive(true)
            
            avPlayer = AVPlayer(playerItem: playerItem)
            avPlayer?.play()
        } catch {
            print("ERROR")
        }
#endif
    }
    
    func playBeep() {
#if os(iOS)
        if let path = Bundle.main.path(forResource: "short_beep", ofType: "m4a") {
            do {
                try AVAudioSession.sharedInstance().setCategory(.playback,
                                                                mode: .default,
                                                                options: [.mixWithOthers])
                try AVAudioSession.sharedInstance().setActive(true)

                audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
                audioPlayer?.play()
            } catch {
                print("ERROR")
            }
        }
#endif
    }
    
    func playFinished() {
#if os(iOS)
        if let path = Bundle.main.path(forResource: "long_beep", ofType: "m4a") {
            do {
                try AVAudioSession.sharedInstance().setCategory(.playback,
                                                                mode: .default,
                                                                options: [.mixWithOthers])
                try AVAudioSession.sharedInstance().setActive(true)
                
                audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
                audioPlayer?.play()
            } catch {
                print("ERROR")
            }
        }
#endif
    }
}

extension BridgeModule: WCSessionDelegate {
    func sendResetToWatch() {
        let watchModel = PhoneToWatchActions.reset
        let data = try! JSONEncoder().encode(watchModel)
        send(data)
    }
    
    func sendWorkoutCompleteToWatch() {
        if WCSession.default.isReachable {
            let model = PhoneToWatchActions.endWorkout
            let data = try! JSONEncoder().encode(model)
            send(data)
        }
    }
    
    func sendCurrentExerciseToWatch() {
        if let currentExercise = currentExerciseInfo.currentExercise,
           let duration = currentExercise.duration ,
           duration > 0 {
            let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date())
            let model = PhoneToWatchActions.inExercise(watchModel)
            let data = try! JSONEncoder().encode(model)
            send(data)
        } else {
            if let currentExercise = currentExerciseInfo.currentExercise,
               let reps = currentExercise.reps,
               reps > 0 {
                
                // if not a timer we need to set the watch display with number of reps
                // if timer it will set when timer updates
                let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date())
                let model = PhoneToWatchActions.inExercise(watchModel)
                let data = try! JSONEncoder().encode(model)
                self.send(data)
            }
        }
    }
    
    func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
        if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) {
            switch model {
            case .nextExercise:
                nextExercise()
                playFinished()
            case .workoutComplete(let data):
                DispatchQueue.main.async {
                    let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
                    self.healthKitUUID = model.healthKitUUID
                }
            case .restartExercise:
                restartExercise()
            case .previousExercise:
                previousExercise()
            case .stopWorkout:
                completeWorkout()
            case .pauseWorkout:
                pauseWorkout()
            }
        }
    }

    
    func session(_ session: WCSession,
                 activationDidCompleteWith activationState: WCSessionActivationState,
                 error: Error?) {
        switch activationState {
        case .notActivated:
            print("notActivated")
        case .inactive:
            print("inactive")
        case .activated:
            print("activated")
#if os(iOS)
            let workoutConfiguration = HKWorkoutConfiguration()
            workoutConfiguration.activityType = .functionalStrengthTraining
            workoutConfiguration.locationType = .indoor
            if WCSession.isSupported(), WCSession.default.activationState == .activated, WCSession.default.isWatchAppInstalled {
                HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in
                    print(error.debugDescription)
                })
            }
#endif
        }
    }
#if os(iOS)
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        session.activate()
    }
#endif
    func send(_ data: Data) {
        guard WCSession.default.activationState == .activated else {
            return
        }
#if os(iOS)
        guard WCSession.default.isWatchAppInstalled else {
            return
        }
#else
        guard WCSession.default.isCompanionAppInstalled else {
            return
        }
#endif
        WCSession.default.sendMessageData(data, replyHandler: nil) { error in
            print("Cannot send message: \(String(describing: error))")
        }
    }
}
