//
//  WatchMainViewMoel.swift
//  Werkout_watch Watch App
//
//  Created by Trey Tartt on 6/22/23.
//

import Foundation
import WatchConnectivity
import SwiftUI
import HealthKit

class WatchMainViewModel:  NSObject, ObservableObject  {
    static let shared = WatchMainViewModel()
    var session: WCSession
    
    @Published var isInWorkout = false
    @Published var watchPackageModel = WatchMainViewModel.defualtPackageModle
    
    @Published var heartValue: Int?
    
    static var defualtPackageModle: WatchPackageModel {
        WatchPackageModel(currentExerciseName: "", currentTimeLeft: -1, workoutStartDate: Date())
    }
    
    let healthStore = HKHealthStore()
    var hkWorkoutSession: HKWorkoutSession?
    var hkBuilder: HKLiveWorkoutBuilder?
    var heartRates = [Int]()
    
    override init() {
        session = WCSession.default
        super.init()
        
        session.delegate = self
        session.activate()
        autorizeHealthKit()
    }
    
    func autorizeHealthKit() {
        let healthKitTypes: Set = [
            HKObjectType.quantityType(forIdentifier: .heartRate)!,
            HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
            HKObjectType.quantityType(forIdentifier: .oxygenSaturation)!,
            HKQuantityType.workoutType()
        ]
        healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
            if !succ {
                fatalError("Error requesting authorization from health store: \(String(describing: error)))")
            }
        }
    }

    func nextExercise() {
        let nextExerciseAction = WatchActions.nextExercise
        let data = try! JSONEncoder().encode(nextExerciseAction)
        send(data)
    }
    
    func restartExercise() {
        let nextExerciseAction = WatchActions.restartExercise
        let data = try! JSONEncoder().encode(nextExerciseAction)
        send(data)
    }
    
    func previousExercise() {
        let nextExerciseAction = WatchActions.previousExercise
        let data = try! JSONEncoder().encode(nextExerciseAction)
        send(data)
    }
    
    func completeWorkout() {
        let nextExerciseAction = WatchActions.stopWorkout
        let data = try! JSONEncoder().encode(nextExerciseAction)
        send(data)
    }
    
    func pauseWorkout() {
        let nextExerciseAction = WatchActions.pauseWorkout
        let data = try! JSONEncoder().encode(nextExerciseAction)
        send(data)
    }
}

extension WatchMainViewModel {
    func initWorkout() -> Bool {
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .functionalStrengthTraining
        configuration.locationType = .indoor
        
        do {
            hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
            hkBuilder = hkWorkoutSession?.associatedWorkoutBuilder()
        } catch {
            fatalError("Unable to create the workout session!")
        }
        guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else {
            return false
        }
        // Setup session and builder.
        hkWorkoutSession.delegate = self
        hkBuilder.delegate = self
        
        /// Set the workout builder's data source.
        hkBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
                                                       workoutConfiguration: configuration)
        return true
    }
    
    func startWorkout() {
        // Initialize our workout
        if initWorkout() {
            
            guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else {
                return
            }
            
            // Start the workout session and begin data collection
            hkWorkoutSession.startActivity(with: Date())
            hkBuilder.beginCollection(withStart: Date()) { (succ, error) in
                if !succ {
                    fatalError("Error beginning collection from builder: \(String(describing: error)))")
                }
            }
            isInWorkout = true
        } else {
            print("didn not init workout")
        }
    }
    
    func stopWorkout(sendDetails: Bool) {
        guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else {
            return
        }
        
        hkWorkoutSession.end()
        hkBuilder.endCollection(withEnd: Date()) { (success, error) in
            hkBuilder.finishWorkout { (workout, error) in
                DispatchQueue.main.async() {
                    self.hkWorkoutSession = nil
                    self.hkBuilder = nil
                    self.heartRates.removeAll()
                    self.isInWorkout = false
                    
                    guard let id = workout?.uuid else {
                        return
                    }
                    let watchFinishWorkoutModel = WatchFinishWorkoutModel(healthKitUUID: id)
                    let data = try! JSONEncoder().encode(watchFinishWorkoutModel)
                    let watchAction = WatchActions.workoutComplete(data)
                    let watchActionData = try! JSONEncoder().encode(watchAction)
                    
                    if sendDetails {
                        self.send(watchActionData)
                    }
                }
            }
        }
    }
}

extension WatchMainViewModel: WCSessionDelegate {
    func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
        if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
            DispatchQueue.main.async {
                switch model {
                case .inExercise(let data):
                    if self.isInWorkout == false {
                        self.startWorkout()
                    }
                    self.watchPackageModel = data
                case .reset:
                    self.isInWorkout = false
                    self.watchPackageModel = WatchMainViewModel.defualtPackageModle
                    self.stopWorkout(sendDetails: false)
                case .endWorkout:
                    self.isInWorkout = false
                    self.watchPackageModel = WatchMainViewModel.defualtPackageModle
                    self.stopWorkout(sendDetails: true)
                }
            }
        }
    }
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("activation did complete")
    }
    
    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))")
        }
    }
}

extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
    func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
        print("[workoutSession] Changed State: \(toState.rawValue)")
    }
    
    func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
        print("[workoutSession] Encountered an error: \(error)")
    }
    
    func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
        for type in collectedTypes {
            guard let quantityType = type as? HKQuantityType else {
                return
            }
            switch quantityType {
            case HKQuantityType.quantityType(forIdentifier: .heartRate):
                DispatchQueue.main.async() {
                    let statistics = workoutBuilder.statistics(for: quantityType)
                    let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
                    let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
                    self.heartValue = Int(Double(round(1 * value!) / 1))
                    self.heartRates.append(Int(Double(round(1 * value!) / 1)))
                    print("[workoutBuilder] Heart Rate: \(String(describing: self.heartValue))")
                }
            default:
                return
            }
        }
    }
    
    func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
        guard let workoutEventType = workoutBuilder.workoutEvents.last?.type else { return }
        print("[workoutBuilderDidCollectEvent] Workout Builder changed event: \(workoutEventType.rawValue)")
    }
}
