//
//  TwilioAudioCallVC.swift
//  WoWonder
//
//  Created by iMac on 13/10/23.
//  Copyright © 2023 ScriptSun. All rights reserved.
//

import UIKit
import AVFoundation
import SDWebImage
import Toast_Swift
import Async
import WowonderMessengerSDK
import CallKit
import TwilioVideo

class TwilioAudioCallVC: BaseVC {
    
    // MARK: - IBOutlets
    
    @IBOutlet weak var userNameLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var userImageView: UIImageView!
    
    // MARK: - Properties
    
    var incomingCallData: UserCallData?
    var outgoingCallData: TwilioCallSuccessModel?
    var userData: UserData?
    var room_name = ""
    var call_id = ""
    private var checkCallTimer = Timer()
    private var callOutgoingCount = 0
    private var callDeclinedTimer = Timer()
    private var callAudioPlayer: AVAudioPlayer?
    private var callTimer = Timer()
    private var seconds = 0
    private var isJoined = false
    
    var room: Room?
    var audioDevice: DefaultAudioDevice = DefaultAudioDevice()
    var localAudioTrack: LocalAudioTrack?
    var remoteParticipant: RemoteParticipant?
    
    override var prefersHomeIndicatorAutoHidden: Bool {
        return self.room != nil
    }
    
    // MARK: - View Life Cycles
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        
        self.initialConfig()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        if let callData = self.incomingCallData {
            self.connectVoiceCall(accessToken: callData.data?.access_token ?? "", uuid: UUID(), room_name: self.room_name)
        } else {
            self.connectVoiceCall(accessToken: self.outgoingCallData?.access_token_2 ?? "", uuid: UUID(), room_name: self.room_name)
        }
    }
    
    // MARK: - Selectors
    
    // Back Button Action
    @IBAction func backButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        self.declineTwilioCall(call_id: self.call_id)
    }
    
    // Audio Mute Button Action
    @IBAction func audioMuteButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        sender.isSelected = !sender.isSelected
        if let localAudioTrack = self.localAudioTrack {
            localAudioTrack.isEnabled = !sender.isSelected
        }
    }
    
    // Speaker Button Action
    @IBAction func speakerButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        sender.isSelected = !sender.isSelected
        self.switchToMicAndSpeaker(isSpeaker: sender.isSelected)
    }
    
    // Hangup Button Action
    @IBAction func hangupButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        self.declineTwilioCall(call_id: self.call_id)
    }
    
    // MARK: - Helper Functions
    
    // Initial Config
    func initialConfig() {
        self.setupTwilio()
        self.setData()
    }
    
    // Set Data
    func setData() {
        if let callData = self.incomingCallData {
            let url = URL(string: callData.avatar ?? "")
            let indicator = SDWebImageActivityIndicator.medium
            self.userImageView.sd_imageIndicator = indicator
            DispatchQueue.global(qos: .userInteractive).async {
                self.userImageView.sd_setImage(with: url, placeholderImage: UIImage(named: "dm_image"))
            }
            self.userNameLabel.text = callData.name ?? ""
            self.descriptionLabel.text = "Waiting for answer"
        } else {
            self.playCallSound()
            self.checkCallTimer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(self.checkOutgoingCall), userInfo: nil, repeats: true)
            let url = URL(string: self.userData?.avatar ?? "")
            let indicator = SDWebImageActivityIndicator.medium
            self.userImageView.sd_imageIndicator = indicator
            DispatchQueue.global(qos: .userInteractive).async {
                self.userImageView.sd_setImage(with: url, placeholderImage: UIImage(named: "dm_image"))
            }
            self.userNameLabel.text = self.userData?.name ?? ""
            self.descriptionLabel.text = "Calling"
        }
    }
    
    func setupTwilio() {
        TwilioVideoSDK.audioDevice = self.audioDevice;
    }
    
    func prepareLocalMedia() {
        if (self.localAudioTrack == nil) {
            self.localAudioTrack = LocalAudioTrack()
            
            if (self.localAudioTrack == nil) {
                print("Failed to create audio track")
            }
        }
    }
    
    fileprivate func connectVoiceCall(accessToken: String, uuid: UUID, room_name: String) {
        self.prepareLocalMedia()
        let connectOptions = ConnectOptions(token: accessToken) { (builder) in
            builder.audioTracks = self.localAudioTrack != nil ? [self.localAudioTrack!] : [LocalAudioTrack]()
            
            if let preferredAudioCodec = Settings.shared.audioCodec {
                builder.preferredAudioCodecs = [preferredAudioCodec]
            }
            
            if let encodingParameters = Settings.shared.getEncodingParameters() {
                builder.encodingParameters = encodingParameters
            }
            
            if let signalingRegion = Settings.shared.signalingRegion {
                builder.region = signalingRegion
            }
            
            builder.roomName = room_name
            builder.uuid = uuid
        }
        
        room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)
        print("Attempting to connect to room \(String(describing: room_name))")
        self.showRoomUI(inRoom: true)
    }
    
    func showRoomUI(inRoom: Bool) {
        UIApplication.shared.isIdleTimerDisabled = inRoom
        self.setNeedsUpdateOfHomeIndicatorAutoHidden()
    }
    
    func switchToMicAndSpeaker(isSpeaker: Bool) {
        if isSpeaker {
            // This block will revert the audio device to the default behavior, which is routing the audio through the speakers
            self.audioDevice.block = {
                DefaultAudioDevice.DefaultAVAudioSessionConfigurationBlock()
                do {
                    try AVAudioSession.sharedInstance().setMode(.voiceChat)
                    try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
                } catch {
                    print("AVAudioSession setMode:error: \(error)")
                }
            }
        } else {
            // This block will first configure the audio device for the default behavior and then will switch the mode to VoiceChat, which will cause the audio output to be routed to the receiver
            self.audioDevice.block = {
                DefaultAudioDevice.DefaultAVAudioSessionConfigurationBlock()
                do {
                    try AVAudioSession.sharedInstance().setMode(.voiceChat)
                    try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
                } catch {
                    print("AVAudioSession setMode:error: \(error)")
                }
            }
        }
        audioDevice.block()
    }
    
    func cleanupRemoteParticipant() {
        if self.remoteParticipant != nil {
            self.remoteParticipant = nil
        }
    }
    
    private func onAnswerCall() {
        self.isJoined = true
        self.invalidateCallTimers()
        self.stopCallSound()
        self.callTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.setCallTimerToLabel), userInfo: nil, repeats: true)
        if self.incomingCallData == nil {
            self.setCallLog(id: String(self.outgoingCallData?.id ?? 0), user_id: self.userData?.user_id, avatar: self.userData?.avatar, name: self.userData?.name, from_id: AppInstance.instance.userId, active: self.userData?.active, time: Date().timeStamp(), status: "Outgoing", room_name: self.outgoingCallData?.room_name, call_type: "audio", type: "Answered")
        }
    }
    
    @objc private func setCallTimerToLabel() {
        self.seconds += 1
        self.descriptionLabel.text = self.getSecondsString()
    }
    
    private func getSecondsString() -> String {
        let hours = self.seconds / 3600
        let minutes = self.seconds / 60 % 60
        let seconds = self.seconds % 60
        return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
    }
    
    private func invalidateCallTimers() {
        self.checkCallTimer.invalidate()
        self.callDeclinedTimer.invalidate()
    }
    
    private func closeScreen() {
        self.invalidateCallTimers()
        self.callTimer.invalidate()
        self.stopCallSound()
        self.cleanupRemoteParticipant()
        self.room = nil
        self.localAudioTrack = nil
        self.showRoomUI(inRoom: false)
        self.navigationController?.popToRootViewController(animated: true)
    }
    
    @objc private func checkOutgoingCall() {
        self.checkForTwilioCallAction(call_id: self.call_id)
    }
    
    @objc private func incrementOutgoingCallDeclinedCount() {
        self.callOutgoingCount += 1
    }
    
    private func playCallSound() {
        guard let url = Bundle.main.url(forResource: "mystic_call", withExtension: "mp3") else { return }
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
            try AVAudioSession.sharedInstance().setActive(true)
            self.callAudioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
            guard let aPlayer = callAudioPlayer else { return }
            aPlayer.play()
        } catch let error {
            print(error.localizedDescription)
        }
    }
    
    private func stopCallSound() {
        self.callAudioPlayer?.stop()
    }
    
}

// MARK: - Extensions

// MARK: Api Call
extension TwilioAudioCallVC {
    
    func checkForTwilioCallAction(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            guard self.callOutgoingCount < 6 else {
                self.declineTwilioCall(call_id: call_id)
                return
            }
            Async.background {
                TwilloCallmanager.instance.checkForTwilloCall(access_token: AppInstance.instance._sessionId, call_id: call_id, call_type: "audio", completionBlock: { (success, sessionError, serverError, error) in
                    if let success = success {
                        Async.main {
                            self.dismissProgressDialog {
                                switch success._callStatusObject {
                                case .answered:
                                    self.invalidateCallTimers()
                                case .declined:
                                    print("declined")
                                    if self.incomingCallData == nil {
                                        self.setCallLog(id: String(self.outgoingCallData?.id ?? 0), user_id: self.userData?.user_id, avatar: self.userData?.avatar, name: self.userData?.name, from_id: AppInstance.instance.userId, active: self.userData?.active, time: Date().timeStamp(), status: "Missing", room_name: self.outgoingCallData?.room_name, call_type: "audio", type: "NoAnswer")
                                    }
                                    self.closeScreen()
                                    break
                                case .calling, .no_answer:
                                    if !self.callDeclinedTimer.isValid {
                                        self.callDeclinedTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.incrementOutgoingCallDeclinedCount), userInfo: nil, repeats: true)
                                    }
                                    break
                                case .unknown:
                                    break
                                }
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(sessionError?.errors?.error_text)
                            }
                        }
                    } else if serverError != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(serverError?.errors?.error_text)
                            }
                        }
                    } else {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(error?.localizedDescription)
                            }
                        }
                    }
                })
            }
        } else {
            self.dismissProgressDialog {
                self.view.makeToast(InterNetError)
            }
        }
    }
    
    private func declineTwilioCall(call_id: String) {
        if Connectivity.isConnectedToNetwork() {
            Async.background {
                TwilloCallmanager.instance.twilioCallAction(access_token: AppInstance.instance._sessionId, call_id: call_id, action: "decline", call_type: "audio", completionBlock: { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                if self.incomingCallData == nil {
                                    if !self.isJoined {
                                        self.setCallLog(id: String(self.outgoingCallData?.id ?? 0), user_id: self.userData?.user_id, avatar: self.userData?.avatar, name: self.userData?.name, from_id: AppInstance.instance.userId, active: self.userData?.active, time: Date().timeStamp(), status: "Missing", room_name: self.outgoingCallData?.room_name, call_type: "audio", type: "NoAnswer")
                                    }
                                }
                                self.closeScreen()
                            }
                        }
                    } else if sessionError != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(sessionError?.errors?.error_text)
                            }
                        }
                    } else if serverError != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(serverError?.errors?.error_text)
                            }
                        }
                    } else {
                        Async.main {
                            self.dismissProgressDialog {
                                self.view.makeToast(error?.localizedDescription)
                            }
                        }
                    }
                })
            }
        } else {
            self.dismissProgressDialog {
                self.view.makeToast(InterNetError)
            }
        }
    }
    
}

// MARK: RoomDelegate Methods
extension TwilioAudioCallVC: RoomDelegate {
    
    func roomDidConnect(room: Room) {
        print("Connected to room \(room.name) as \(room.localParticipant?.identity ?? "")")
        for remoteParticipant in room.remoteParticipants {
            remoteParticipant.delegate = self
        }
    }
    
    func roomDidDisconnect(room: Room, error: Error?) {
        print("Disconnected from room \(room.name), error = \(String(describing: error))")
        self.closeScreen()
    }
    
    func roomDidFailToConnect(room: Room, error: Error) {
        print("Failed to connect to room with error: \(error.localizedDescription)")
        self.room = nil
        self.showRoomUI(inRoom: false)
    }
    
    func participantDidConnect(room: Room, participant: RemoteParticipant) {
        participant.delegate = self
        print("Participant \(participant.identity) connected with \(participant.remoteAudioTracks.count) audio and \(participant.remoteVideoTracks.count) video tracks")
    }
    
    func participantDidDisconnect(room: Room, participant: RemoteParticipant) {
        print("Room \(room.name), Participant \(participant.identity) disconnected")
    }
    
}

// MARK: - RemoteParticipantDelegate
extension TwilioAudioCallVC : RemoteParticipantDelegate {
    
    func remoteParticipantDidPublishVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        // Remote Participant has offered to share the video Track.
        
        print("Participant \(participant.identity) published video track")
    }
    
    func remoteParticipantDidUnpublishVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        // Remote Participant has stopped sharing the video Track.
        
        print("Participant \(participant.identity) unpublished video track")
    }
    
    func remoteParticipantDidPublishAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        // Remote Participant has offered to share the audio Track.
        
        print("Participant \(participant.identity) published audio track")
    }
    
    func remoteParticipantDidUnpublishAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        print("Participant \(participant.identity) unpublished audio track")
    }
    
    func didSubscribeToVideoTrack(videoTrack: RemoteVideoTrack, publication: RemoteVideoTrackPublication, participant: RemoteParticipant) {
        // The LocalParticipant is subscribed to the RemoteParticipant's video Track. Frames will begin to arrive now.
        
        print("Subscribed to \(publication.trackName) video track for Participant \(participant.identity)")
    }
    
    func didUnsubscribeFromVideoTrack(videoTrack: RemoteVideoTrack, publication: RemoteVideoTrackPublication, participant: RemoteParticipant) {
        // We are unsubscribed from the remote Participant's video Track. We will no longer receive the
        // remote Participant's video.
        
        print("Unsubscribed from \(publication.trackName) video track for Participant \(participant.identity)")
    }
    
    func didSubscribeToAudioTrack(audioTrack: RemoteAudioTrack, publication: RemoteAudioTrackPublication, participant: RemoteParticipant) {
        // We are subscribed to the remote Participant's audio Track. We will start receiving the
        // remote Participant's audio now.
        
        print("Subscribed to audio track for Participant \(participant.identity)")
        self.remoteParticipant = participant
        self.audioDevice.isEnabled = true
        self.switchToMicAndSpeaker(isSpeaker: false)
        self.onAnswerCall()
    }
    
    func didUnsubscribeFromAudioTrack(audioTrack: RemoteAudioTrack, publication: RemoteAudioTrackPublication, participant: RemoteParticipant) {
        // We are unsubscribed from the remote Participant's audio Track. We will no longer receive the
        // remote Participant's audio.
        
        print("Unsubscribed from audio track for Participant \(participant.identity)")
        self.audioDevice.isEnabled = false
        if self.remoteParticipant == participant {
            self.closeScreen()
        }
    }
    
    func remoteParticipantDidEnableVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        print("Participant \(participant.identity) enabled video track")
    }
    
    func remoteParticipantDidDisableVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        print("Participant \(participant.identity) disabled video track")
    }
    
    func remoteParticipantDidEnableAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        print("Participant \(participant.identity) enabled audio track")
    }
    
    func remoteParticipantDidDisableAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        print("Participant \(participant.identity) disabled audio track")
    }
    
    func didFailToSubscribeToAudioTrack(publication: RemoteAudioTrackPublication, error: Error, participant: RemoteParticipant) {
        print("FailedToSubscribe \(publication.trackName) audio track, error = \(String(describing: error))")
    }
    
    func didFailToSubscribeToVideoTrack(publication: RemoteVideoTrackPublication, error: Error, participant: RemoteParticipant) {
        print("FailedToSubscribe \(publication.trackName) video track, error = \(String(describing: error))")
    }
    
}
