//
//  NasaLocalNotificationBuilder.swift
//
//  Created by Fabrizio Duroni on 06/08/2018.
//  2018 Fabrizio Duroni.
//

import Foundation
import UserNotifications
import UIKit
import Async
import WowonderMessengerSDK

class LocalNotificationBuilder: NSObject {
    
    static let shared = LocalNotificationBuilder()
    private var checkCallTimer = Timer()
    private var callDeclinedTimer = Timer()
    private var callDeclinedCount = 0
    private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    private let notificationCenter: UNUserNotificationCenter = UNUserNotificationCenter.current()
    private var notificationActions: [UNNotificationAction] = []
    private var notificationContent = UNMutableNotificationContent()
    var callData: UserCallData?
    var callProvider: CallProvider = .agora
    var callType: CallType = .audio
    
    func registerNotification(_ application: UIApplication) {
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: {_ , _ in })
        DispatchQueue.main.async {
            application.registerForRemoteNotifications()
        }
    }
    
    func setActions() -> LocalNotificationBuilder {
        self.notificationActions.append(
            UNNotificationAction(identifier: "answer", title: "Answer", options: [.foreground, .authenticationRequired])
        )
        self.notificationActions.append(
            UNNotificationAction(identifier: "dismiss", title: "Dismiss", options: [.foreground, .authenticationRequired])
        )
        return self
    }
    
    func setCategory() {
        let notificationCategory = UNNotificationCategory(identifier: "wowonder_call_notification", actions: self.notificationActions, intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: "", options: .customDismissAction)
        self.notificationCenter.setNotificationCategories([notificationCategory])
    }
    
    func setContent(callType: String, callProvider: String, data: UserCallData?) -> LocalNotificationBuilder {
        var userInfo = data?.getStringAnyData()
        userInfo?["callType"] = callType
        userInfo?["callProvider"] = callProvider
        
        if callType == "audio" {
            self.notificationContent.title = "Voice Call"
        }
        if callType == "video" {
            self.notificationContent.title = "Video Call"
        }
        self.notificationContent.body = (data?.name ?? "") + " is calling you"
        self.notificationContent.sound = UNNotificationSound.default
        self.notificationContent.categoryIdentifier = "wowonder_call_notification"
        self.notificationContent.userInfo = userInfo ?? [:]
        return self
    }
    
    func build(notificationIdentifier: String) {
        var getCallNotificationData = UserDefaults.standard.getCallNotification(Key: "Call_Local_Notification")
        let data = try! JSONSerialization.data(withJSONObject: getCallNotificationData, options: [])
        let callNotificationData = try! JSONDecoder().decode([UserCallData].self, from: data)
        if callNotificationData.contains(where: { $0.data?.id == notificationIdentifier }) {
            print("Notification Is Already Dilivered")
        } else {
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
            let request = UNNotificationRequest(identifier: notificationIdentifier, content: self.notificationContent, trigger: trigger)
            self.notificationCenter.delegate = self
            self.notificationCenter.add(request) { error in
                if let error = error {
                    print(error.localizedDescription)
                } else {
                    if let userInfo = request.content.userInfo as? [String: Any] {
                        getCallNotificationData.append(userInfo)
                        UserDefaults.standard.setCallNotification(value: getCallNotificationData, ForKey: "Call_Local_Notification")
                        print("Notification Triggered")
                        if userInfo["callProvider"] as? String == "agora" {
                            self.callProvider = .agora
                        } else if userInfo["callProvider"] as? String == "twilio" {
                            self.callProvider = .twilio
                        }
                        if userInfo["callType"] as? String == "audio" {
                            self.callType = .audio
                        } else if userInfo["callType"] as? String == "video" {
                            self.callType = .video
                        }
                        let data = try! JSONSerialization.data(withJSONObject: userInfo, options: [])
                        self.callData = try! JSONDecoder().decode(UserCallData.self, from: data)
                        DispatchQueue.main.async {
                            self.checkCallTimer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(self.checkForCallStatus), userInfo: nil, repeats: true)
                        }
                    }
                }
            }
        }
    }
    
    @objc func checkForCallStatus() {
        switch self.callProvider {
        case .agora:
            self.checkForAgoraCallAction(call_id: self.callData?.data?.id ?? "")
        case .twilio:
            switch self.callType {
            case .audio:
                self.checkForTwilioCallAction(call_id: self.callData?.data?.id ?? "", call_type: "audio")
            case .video:
                self.checkForTwilioCallAction(call_id: self.callData?.data?.id ?? "", call_type: "video")
            }
        }
    }
    
    @objc private func incrementCallDeclinedCount() {
        self.callDeclinedCount += 1
    }
    
    private func setMissedCallNotification(call_id: String) {
        let content = UNMutableNotificationContent()
        switch self.callType {
        case .audio:
            content.title = "Voice Call"
        case .video:
            content.title = "Video Call"
        }
        content.body = "You Missed " + (self.callData?.name ?? "") + "'s Call"
        content.categoryIdentifier = call_id
        content.sound = UNNotificationSound.default
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
        let request = UNNotificationRequest(identifier: call_id, content: content, trigger: trigger)
        self.notificationCenter.delegate = self
        self.notificationCenter.add(request) { error in
            if let error = error {
                print(error.localizedDescription)
            } else {
                print("Notification Triggered")
            }
        }
    }
    
}

// MARK: - Extensions

// MARK: UNUserNotificationCenterDelegate Methods
extension LocalNotificationBuilder: UNUserNotificationCenterDelegate {
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .badge, .banner])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        if response.notification.request.content.categoryIdentifier == "wowonder_call_notification" {
            if let userInfo = response.notification.request.content.userInfo as? [String: Any] {
                print(userInfo)
                let data = try! JSONSerialization.data(withJSONObject: userInfo, options: [])
                self.callData = try! JSONDecoder().decode(UserCallData.self, from: data)
                if userInfo["callProvider"] as? String == "agora" {
                    self.callProvider = .agora
                } else if userInfo["callProvider"] as? String == "twilio" {
                    self.callProvider = .twilio
                }
                if userInfo["callType"] as? String == "audio" {
                    self.callType = .audio
                } else if userInfo["callType"] as? String == "video" {
                    self.callType = .video
                }
                if response.actionIdentifier == "answer" {
                    switch self.callProvider {
                    case .agora:
                        self.answerAgoraCall(call_id: self.callData?.data?.id ?? "")
                    case .twilio:
                        switch self.callType {
                        case .audio:
                            self.answerTwilioCall(call_id: self.callData?.data?.id ?? "", call_type: "audio")
                        case .video:
                            self.answerTwilioCall(call_id: self.callData?.data?.id ?? "", call_type: "video")
                        }
                    }
                } else if response.actionIdentifier == "dismiss" {
                    switch self.callProvider {
                    case .agora:
                        self.declineAgoraCall(call_id: self.callData?.data?.id ?? "")
                    case .twilio:
                        switch self.callType {
                        case .audio:
                            self.declineTwilioCall(call_id: self.callData?.data?.id ?? "", call_type: "audio")
                        case .video:
                            self.declineTwilioCall(call_id: self.callData?.data?.id ?? "", call_type: "video")
                        }
                    }
                } else {
                    if let navVC = (appDelegate.window?.rootViewController as? UINavigationController) {
                        if let newVC = R.storyboard.call.incomingCallVC() {
                            newVC.callProvider = self.callProvider
                            newVC.callType = self.callType
                            newVC.callData = self.callData
                            if !navVC.viewControllers.contains(where: { $0 is IncomingCallVC }) {
                                navVC.pushViewController(newVC, animated: true)
                            } else {
                                print("Already Presented")
                            }
                        }
                    }
                }
            }
        } else {
            print("missed call notification tapped...")
        }
    }
    
}

// MARK: Api Call
extension LocalNotificationBuilder {
    
    func checkForAgoraCallAction(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            guard self.callDeclinedCount < 6 else {
                self.closeAgoraCall(call_id: call_id)
                return
            }
            Async.background {
                CallManager.instance.checkForAgoraCall(access_token: AppInstance.instance._sessionId, call_id: call_id) { (success, sessionError, serverError, error) in
                    if let success = success {
                        Async.main {
                            switch success._callStatusObject {
                            case .answered:
                                self.checkCallTimer.invalidate()
                                self.callDeclinedTimer.invalidate()
                                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                            case .declined, .no_answer:
                                self.checkCallTimer.invalidate()
                                self.callDeclinedTimer.invalidate()
                                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                                self.setMissedCallNotification(call_id: call_id)
                                break
                            case .calling:
                                if !self.callDeclinedTimer.isValid {
                                    self.callDeclinedTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.incrementCallDeclinedCount), userInfo: nil, repeats: true)
                                }
                                break
                            case .unknown:
                                break
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                }
            }
        } else {
            print(InterNetError)
        }
    }
    
    func checkForTwilioCallAction(call_id: String, call_type: String) {
        if Connectivity.isConnectedToNetwork() {
            guard self.callDeclinedCount < 6 else {
                self.closeTwilioCall(call_id: call_id, call_type: call_type)
                return
            }
            Async.background {
                TwilloCallmanager.instance.checkForTwilloCall(access_token: AppInstance.instance._sessionId, call_id: call_id, call_type: call_type, completionBlock: { (success, sessionError, serverError, error) in
                    if let success = success {
                        Async.main {
                            switch success._callStatusObject {
                            case .answered:
                                self.checkCallTimer.invalidate()
                                self.callDeclinedTimer.invalidate()
                                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                            case .declined:
                                self.checkCallTimer.invalidate()
                                self.callDeclinedTimer.invalidate()
                                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                                self.setMissedCallNotification(call_id: call_id)
                            case .calling, .no_answer:
                                if !self.callDeclinedTimer.isValid {
                                    self.callDeclinedTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.incrementCallDeclinedCount), userInfo: nil, repeats: true)
                                }
                            case .unknown:
                                break
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                })
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func closeAgoraCall(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                CallManager.instance.agoraCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "close") { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            print("close")
                            self.checkCallTimer.invalidate()
                            self.callDeclinedTimer.invalidate()
                            self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                            self.setMissedCallNotification(call_id: call_id)
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                }
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func closeTwilioCall(call_id: String, call_type: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                TwilloCallmanager.instance.twilioCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "close", call_type: call_type, completionBlock: { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            print("close")
                            self.checkCallTimer.invalidate()
                            self.callDeclinedTimer.invalidate()
                            self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                            self.setMissedCallNotification(call_id: call_id)
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                })
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func answerAgoraCall(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                CallManager.instance.agoraCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "answer") { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            if success?.api_status == 200 {
                                DispatchQueue.main.async {
                                    self.checkCallTimer.invalidate()
                                    self.callDeclinedTimer.invalidate()
                                    if let navVC = (appDelegate.window?.rootViewController as? UINavigationController) {
                                        switch self.callType {
                                        case .audio:
                                            if let newVC = R.storyboard.call.agoraAudioCallVC() {
                                                newVC.incomingCallData = self.callData
                                                newVC.call_id = self.callData?.data?.id ?? ""
                                                newVC.room_name = self.callData?.data?.room_name ?? ""
                                                navVC.pushViewController(newVC, animated: true)
                                            }
                                        case .video:
                                            if let newVC = R.storyboard.call.agoraVideoCallVC() {
                                                newVC.incomingCallData = self.callData
                                                newVC.call_id = self.callData?.data?.id ?? ""
                                                newVC.room_name = self.callData?.data?.room_name ?? ""
                                                navVC.pushViewController(newVC, animated: true)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                }
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func answerTwilioCall(call_id: String, call_type: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                TwilloCallmanager.instance.twilioCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "answer", call_type: call_type, completionBlock: { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            if success?.api_status == 200 {
                                DispatchQueue.main.async {
                                    self.checkCallTimer.invalidate()
                                    self.callDeclinedTimer.invalidate()
                                    if let navVC = (appDelegate.window?.rootViewController as? UINavigationController) {
                                        switch self.callType {
                                        case .audio:
                                            if let newVC = R.storyboard.call.twilioAudioCallVC() {
                                                newVC.incomingCallData = self.callData
                                                newVC.call_id = self.callData?.data?.id ?? ""
                                                newVC.room_name = self.callData?.data?.room_name ?? ""
                                                navVC.pushViewController(newVC, animated: true)
                                            }
                                        case .video:
                                            if let newVC = R.storyboard.call.twilioVideoCallVC() {
                                                newVC.incomingCallData = self.callData
                                                newVC.call_id = self.callData?.data?.id ?? ""
                                                newVC.room_name = self.callData?.data?.room_name ?? ""
                                                navVC.pushViewController(newVC, animated: true)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                })
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func declineAgoraCall(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                CallManager.instance.agoraCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "decline") { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            print("declined")
                            self.checkCallTimer.invalidate()
                            self.callDeclinedTimer.invalidate()
                            self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                }
            }
        } else {
            print(InterNetError)
        }
    }
    
    private func declineTwilioCall(call_id: String, call_type: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                TwilloCallmanager.instance.twilioCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "decline", call_type: call_type, completionBlock: { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            print("declined")
                            self.checkCallTimer.invalidate()
                            self.callDeclinedTimer.invalidate()
                            self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [call_id])
                        }
                    } else if sessionError != nil {
                        Async.main {
                            print(sessionError?.errors?.error_text ?? "")
                        }
                    } else if serverError != nil {
                        Async.main {
                            print(serverError?.errors?.error_text ?? "")
                        }
                    } else {
                        Async.main {
                            print(error?.localizedDescription ?? "")
                        }
                    }
                })
            }
        } else {
            print(InterNetError)
        }
    }
    
}
