import Foundation
import SMKitUI
import SMBase

@objc(SMKitUIManager)
class SMKitUIManager: RCTViewManager {
    
    var onWorkoutDidFinish:RCTPromiseResolveBlock?
    var onWorkoutFailed:RCTPromiseRejectBlock?
    
    var summaryData:WorkoutSummaryData?
    var didCompleteWorkout = false
    
    var didReciveSummary = false{
        didSet{
            if didReciveSummary && workoutDidEnd{
                sendResult()
            }
        }
    }
    
    var workoutDidEnd = false{
        didSet{
            if didReciveSummary && workoutDidEnd{
                sendResult()
            }
        }
    }
    
    var smkitUIViewController:UIViewController?{
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        }
        return nil
    }
    
    @objc(configure:onSuccess:onFailure:)
    func configure(key:NSString, onSuccess: @escaping RCTPromiseResolveBlock,onFailure:@escaping RCTPromiseRejectBlock) -> Void {
        var isConfigDone = false
        var didFail = false
        SMKitUIModel.configure(authKey: "\(key)") {
            if !isConfigDone{
                isConfigDone = true
                onSuccess("")
            }
        } onFailure: { err in
            if !didFail{
                didFail = true
                onFailure("Configure Failed", err?.localizedDescription ?? "", err)
            }
        }
    }
    
    @objc(startAssessment:showSummary:userData:forceShowUserDataScreen:customAssessmentID:onWorkoutDidFinish:onWorkoutFailed:)
    func startAssessment(type:NSString, showSummary:Bool, userData:NSString?, forceShowUserDataScreen:Bool, customAssessmentID:String?, onWorkoutDidFinish: @escaping RCTPromiseResolveBlock, onWorkoutFailed:@escaping RCTPromiseRejectBlock){
        DispatchQueue.main.async {[weak self] in
            
            guard let self = self,
                  let smkitUIViewController = smkitUIViewController else {
                onWorkoutFailed("StartAssessment Failed", "Failed to present view", nil)
                return
            }
            
            self.onWorkoutDidFinish = onWorkoutDidFinish
            self.onWorkoutFailed = onWorkoutFailed
            
            do{
                let userData = try self.getUserData(rawJson: userData)
                if let type = AssessmentTypes(rawValue: "\(type)"){
                    try SMKitUIModel.startAssessmet(
                        viewController: smkitUIViewController,
                        type: type,
                        customAssessmentID:customAssessmentID == "" ? nil : customAssessmentID,
                        userData: userData,
                        forceShowUserDataScreen: forceShowUserDataScreen,
                        showSummary: showSummary,
                        delegate: self){error in
                            onWorkoutFailed("StartAssessment Failed", error.localizedDescription, error)
                        }
                }else{
                    onWorkoutFailed("StartAssessment Failed", "Invalid type", nil)
                }
            }catch{
                onWorkoutFailed("StartAssessment Failed", error.localizedDescription, error)
            }
        }
    }
    
    @objc(startCustomWorkout:onWorkoutDidFinish:onWorkoutFailed:)
    func startCustomWorkout(rawJson:String, onWorkoutDidFinish: @escaping RCTPromiseResolveBlock, onWorkoutFailed:@escaping RCTPromiseRejectBlock){
        DispatchQueue.main.async {[weak self] in
            
            guard let self = self,
                  let smkitUIViewController = smkitUIViewController else {
                onWorkoutFailed("StartCustomWorkout Failed", "Failed to present view", nil)
                return
            }
            self.onWorkoutDidFinish = onWorkoutDidFinish
            self.onWorkoutFailed = onWorkoutFailed
            do{
                let json = try stringJsonToDic(stringJSON: rawJson)
                let workout = try SMWorkout(FromJson: json)
                try SMKitUIModel.startWorkout(viewController: smkitUIViewController, workout: workout, delegate: self)
            }catch{
                onWorkoutFailed("StartCustomWorkout Failed", error.localizedDescription, error)
            }
        }
    }
    
    @objc(startCustomAssessment:userData:forceShowUserDataScreen:showSummary:onWorkoutDidFinish:onWorkoutFailed:)
    func startCustomAssessment(rawJson:String, userData:NSString?, forceShowUserDataScreen:Bool, showSummary:Bool, onWorkoutDidFinish: @escaping RCTPromiseResolveBlock, onWorkoutFailed:@escaping RCTPromiseRejectBlock){
        DispatchQueue.main.async { [weak self] in
            guard let self = self,
                  let smkitUIViewController = smkitUIViewController else {
                onWorkoutFailed("startCustomAssessment Failed", "Failed to present view", nil)
                return
            }
            self.onWorkoutDidFinish = onWorkoutDidFinish
            self.onWorkoutFailed = onWorkoutFailed

            do{
                let userData = try self.getUserData(rawJson: userData)
                let json = try stringJsonToDic(stringJSON: rawJson)
                let assessment = try SMWorkoutAssessment.initFromJSON(json)
                try SMKitUIModel.startCustomAssessment(viewController: smkitUIViewController, assessment: assessment, userData:userData, forceShowUserDataScreen: forceShowUserDataScreen, showSummary: showSummary, delegate: self) { error in
                    onWorkoutFailed("startCustomAssessment Failed", error.localizedDescription, error)
                }
            }catch{
                onWorkoutFailed("startCustomAssessment Failed", error.localizedDescription, error)
            }
        }
    }
    
    @objc(startWorkoutProgram:onWorkoutDidFinish:onWorkoutFailed:)
    func startWorkoutProgram(rawJson:String, onWorkoutDidFinish: @escaping RCTPromiseResolveBlock, onWorkoutFailed:@escaping RCTPromiseRejectBlock){
        self.onWorkoutDidFinish = onWorkoutDidFinish
        self.onWorkoutFailed = onWorkoutFailed
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self,
                  let smkitUIViewController = smkitUIViewController else {
                onWorkoutFailed("StartWorkoutProgram Failed", "Failed to present view", nil)
                return
            }
            
            guard let json = try? stringJsonToDic(stringJSON: rawJson),
                  let week = json["week"] as? Int,
                  let zone = json["bodyZone"] as? String,
                  let difficultyLevelRaw = json["difficultyLevel"] as? String,
                  let workoutDurationRaw = json["workoutDuration"] as? String,
                  let rawLanguage = json["language"] as? String,
                  let programID = json["programID"] as? String,
                  let bodyZone = BodyZone(rawValue: zone),
                  let difficultyLevel = WorkoutDifficulty(rawValue: difficultyLevelRaw),
                  let workoutDuration = WorkoutDuration(rawValue: workoutDurationRaw),
                  let language:SencySupportedLanguage = SencySupportedLanguage(rawValue: rawLanguage)
            else {
                onWorkoutFailed("StartWorkoutProgram Failed", "Invalid Workout Config", nil)
                return
            }
            
            let config = WorkoutConfig(
                week: week,
                bodyZone: bodyZone,
                difficultyLevel: difficultyLevel,
                workoutDuration: workoutDuration,
                language: language,
                programID: programID
            )
            
            SMKitUIModel.startWorkoutFromProgram(viewController: smkitUIViewController, workoutConfig: config, delegate: self) { error in
                onWorkoutFailed("StartWorkoutProgram Failed", error.localizedDescription, error)
            }
        }
    }
    
    @objc(setSessionLanguage:)
    func setSessionLanguage(language:String){
        let lang = SencySupportedLanguage(rawValue: language) ?? .English
        SMKitUIModel.setSessionLanguage(language: lang)
    }

    @objc(setEndExercisePreferences:)
    func setEndExercisePreferences(preferencesString:String){
        let target = EndExercisePreferences(rawValue: preferencesString) ?? .Default
        SMKitUIModel.setEndExercisePreferences(endExercisePreferences: target)
    }
    
    @objc(setCounterPreferences:)
    func setCounterPreferences(preferencesString:String){
        let preferences = CounterPreferences(rawValue: preferencesString) ?? .Default
        SMKitUIModel.setCounterPreferences(counterPreferences: preferences)
    }

    private func stringJsonToDic(stringJSON:String) throws -> [String:Any]{
        guard let data = stringJSON.data(using: .utf8),
              let json = try JSONSerialization.jsonObject(with: data) as? [String:Any]
        else { return [:] }
        return json
    }
    
    
    override class func requiresMainQueueSetup() -> Bool {
        true
    }
    
    func getUserData(rawJson:NSString?) throws -> UserData?{
        guard let rawJson = rawJson as? String else { return nil }
        let json = try stringJsonToDic(stringJSON: rawJson)
        return UserData(
            gender: Gender(rawValue: json["gender"] as? String ?? "") ?? .Female,
            birthday: getBirthdayDate(from: json["age"] as? Int ?? 0) ?? Date()
        )
    }
    
    func getBirthdayDate(from age: Int) -> Date? {
        // Create a date components instance to subtract years
        var dateComponents = DateComponents()
        dateComponents.year = -age
        
        // Use the current calendar and current date to calculate the birth date
        let calendar = Calendar.current
        if let birthDate = calendar.date(byAdding: dateComponents, to: Date()) {
            return birthDate
        }
        return nil
    }
    
    func reset(){
        summaryData = nil
        didReciveSummary = false
        workoutDidEnd = false
        didCompleteWorkout = false
    }
}

extension SMKitUIManager:SMKitUIWorkoutDelegate{
    func handleWorkoutErrors(error: any Error) {
        onWorkoutFailed?("Workout Exercise Error", error.localizedDescription, error)
    }
    
    func workoutDidFinish() {
        didCompleteWorkout = true
        workoutDidEnd = true
        
        SMKitUIModel.exitSDK()
    }
    
    func didExitWorkout() {
        didCompleteWorkout = false
        workoutDidEnd = true
        
        SMKitUIModel.exitSDK()
    }
    
    func sendResult(){
        guard let onWorkoutDidFinish = self.onWorkoutDidFinish else {
            onWorkoutFailed?("Unable to create summary", "Missing callback" , nil)
            return
        }
        
        do{
            let result:[String:Any] = [
                "summary": try summaryData?.toStringJson() ?? "",
                "didFinish": didCompleteWorkout
            ]
            onWorkoutDidFinish(result)
            reset()
        }catch{
            onWorkoutFailed?("Unable to create summary", error.localizedDescription, error as NSError)
        }
    }
    
    func exerciseDidFinish(data: ExerciseData) {
        
    }
    
    func didReceiveSummaryData(data: WorkoutSummaryData?) {
        summaryData = data;
        didReciveSummary = true
    }
}

extension WorkoutSummaryData{
    func json() throws -> String{
        let jsonEncoder = JSONEncoder()
        let jsonData = try jsonEncoder.encode(self)
        return String(data: jsonData, encoding: String.Encoding.utf8) ?? ""
    }
}

extension SMWorkoutAssessment{
    static func initFromJSON(_ json: [String:Any]) throws -> SMWorkoutAssessment{
        var assessmentsExercises:[SMAssessmentExercise] = []
        
        if let exercise = json["exercises"] as? [[String:Any]] {
            try exercise.forEach({
                try assessmentsExercises.append(SMAssessmentExercise(FromJson: $0))
            })
        }
        
        return SMWorkoutAssessment(
            id: json["id"] as? String,
            name: json["name"] as? String,
            workoutIntro: json["workoutIntro"] as? String,
            soundtrack: json["soundtrack"] as? String,
            assessmentsExercises: assessmentsExercises,
            getInFrame: json["getInFrame"] as? String,
            bodycalFinished: json["bodycalFinished"] as? String,
            workoutClosure: json["workoutClosure"] as? String
        )
    }
}
