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

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

class TwilioVideoCallVC: BaseVC {
    
    // MARK: - IBOutlets
    
    @IBOutlet weak var userNameLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var remoteVideoView: UIView!
    @IBOutlet weak var userImageView: UIImageView!
    @IBOutlet weak var previewView: VideoView!
    @IBOutlet weak var localVideoMutedView: UIView!
    @IBOutlet weak var localAudioMutedIndicator: UIImageView!
    @IBOutlet weak var localVideoMutedIndicator: UIImageView!
    @IBOutlet weak var remoteAudioMutedIndicator: UIImageView!
    @IBOutlet weak var controlView: UIView!
    
    // MARK: - Properties
    
    var incomingCallData: UserCallData?
    var outgoingCallData: TwilioCallSuccessModel?
    var userData: UserData?
    var room_name = ""
    var call_id = ""
    private var callTimer = Timer()
    private var checkCallTimer = Timer()
    private var callOutgoingCount = 0
    private var callDeclinedTimer = Timer()
    private var seconds = 0
    private var callAudioPlayer: AVAudioPlayer?
    private var isJoined = false
    
    var room: Room?
    var audioDevice: DefaultAudioDevice = DefaultAudioDevice()
    var camera: CameraSource?
    var localVideoTrack: LocalVideoTrack?
    var localAudioTrack: LocalAudioTrack?
    var remoteParticipant: RemoteParticipant?
    var remoteView: VideoView?
    
    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.connectVideoCall(accessToken: callData.data?.access_token ?? "", uuid: UUID(), room_name: self.room_name)
        } else {
            self.connectVideoCall(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)
    }
    
    // Switch Camera Button Action
    @IBAction func switchCameraButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        let frontCamera = CameraSource.captureDevice(position: .front)
        let backCamera = CameraSource.captureDevice(position: .back)
        if (frontCamera != nil || backCamera != nil) {
            if (frontCamera != nil && backCamera != nil) {
                self.flipCamera()
            }
        } else {
            print("No front or back capture device found!")
        }
        self.resetHideButtonsTimer()
    }
    
    // Video Mute Button Action
    @IBAction func videoMuteButtonAction(_ sender: UIButton) {
        self.view.endEditing(true)
        sender.isSelected = !sender.isSelected
        if let localVideoTrack = self.localVideoTrack {
            localVideoTrack.isEnabled = !sender.isSelected
        }
        self.previewView.isHidden = sender.isSelected
        self.localVideoMutedView.isHidden = !sender.isSelected
        self.localVideoMutedIndicator.isHidden = !sender.isSelected
        self.resetHideButtonsTimer()
    }
    
    // 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
        }
        self.localAudioMutedIndicator.isHidden = !sender.isSelected
        self.resetHideButtonsTimer()
    }
    
    // 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()
        self.hideVideoMuted()
        self.setupButtons()
    }
    
    // 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;
        if PlatformUtils.isSimulator {
            self.previewView.removeFromSuperview()
        } else {
            self.startPreview()
        }
    }
    
    func startPreview() {
        if PlatformUtils.isSimulator {
            return
        }
        let frontCamera = CameraSource.captureDevice(position: .front)
        let backCamera = CameraSource.captureDevice(position: .back)
        if (frontCamera != nil || backCamera != nil) {
            self.camera = CameraSource(delegate: self)
            self.localVideoTrack = LocalVideoTrack(source: camera!, enabled: true, name: "Camera")
            self.localVideoTrack!.addRenderer(self.previewView)
            print("Video track created")
            self.camera!.startCapture(device: frontCamera != nil ? frontCamera! : backCamera!) { (captureDevice, videoFormat, error) in
                if let error = error {
                    print("Capture failed with error.\ncode = \((error as NSError).code) error = \(error.localizedDescription)")
                } else {
                    self.previewView.shouldMirror = (captureDevice.position == .front)
                }
            }
        } else {
            print("No front or back capture device found!")
        }
    }
    
    @objc func flipCamera() {
        var newDevice: AVCaptureDevice?
        if let camera = self.camera, let captureDevice = camera.device {
            if captureDevice.position == .front {
                newDevice = CameraSource.captureDevice(position: .back)
            } else {
                newDevice = CameraSource.captureDevice(position: .front)
            }
            if let newDevice = newDevice {
                camera.selectCaptureDevice(newDevice) { (captureDevice, videoFormat, error) in
                    if let error = error {
                        print("Error selecting capture device.\ncode = \((error as NSError).code) error = \(error.localizedDescription)")
                    } else {
                        self.previewView.shouldMirror = (captureDevice.position == .front)
                    }
                }
            }
        }
    }
    
    func setupRemoteVideoView() {
        self.remoteView = VideoView(frame: CGRect.zero, delegate: self)
        self.remoteVideoView.insertSubview(self.remoteView!, at: 0)
        self.remoteView!.contentMode = .scaleAspectFit;
        let centerX = NSLayoutConstraint(item: self.remoteView!, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.remoteVideoView, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0);
        self.remoteVideoView.addConstraint(centerX)
        let centerY = NSLayoutConstraint(item: self.remoteView!, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.remoteVideoView, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: 0);
        self.remoteVideoView.addConstraint(centerY)
        let width = NSLayoutConstraint(item: self.remoteView!, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.remoteVideoView, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1, constant: 0);
        self.remoteVideoView.addConstraint(width)
        let height = NSLayoutConstraint(item: self.remoteView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.remoteVideoView, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1, constant: 0);
        self.remoteVideoView.addConstraint(height)
    }
    
    func prepareLocalMedia() {
        if (self.localAudioTrack == nil) {
            self.localAudioTrack = LocalAudioTrack()
            
            if (self.localAudioTrack == nil) {
                print("Failed to create audio track")
            }
        }
        if (localVideoTrack == nil) {
            self.startPreview()
        }
    }
    
    fileprivate func connectVideoCall(accessToken: String, uuid: UUID, room_name: String) {
        self.prepareLocalMedia()
        let connectOptions = ConnectOptions(token: accessToken) { (builder) in
            builder.audioTracks = self.localAudioTrack != nil ? [self.localAudioTrack!] : [LocalAudioTrack]()
            builder.videoTracks = self.localVideoTrack != nil ? [self.localVideoTrack!] : [LocalVideoTrack]()
            
            if let preferredAudioCodec = Settings.shared.audioCodec {
                builder.preferredAudioCodecs = [preferredAudioCodec]
            }
            
            let preferredVideoCodec = Settings.shared.videoCodec
            if preferredVideoCodec == .auto {
                builder.videoEncodingMode = .auto
            } else if let codec = preferredVideoCodec.codec {
                builder.preferredVideoCodecs = [codec]
            }
            
            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 cleanupRemoteParticipant() {
        if self.remoteParticipant != nil {
            self.remoteView?.removeFromSuperview()
            self.remoteView = nil
            self.remoteParticipant = nil
        }
    }
    
    private func invalidateCallTimers() {
        self.checkCallTimer.invalidate()
        self.callDeclinedTimer.invalidate()
    }
    
    private func closeScreen() {
        self.invalidateCallTimers()
        self.callTimer.invalidate()
        self.stopCallSound()
        self.hideControlView()
        self.cleanupRemoteParticipant()
        self.room = nil
        self.localAudioTrack = nil
        self.localVideoTrack = nil
        self.showRoomUI(inRoom: false)
        self.navigationController?.popToRootViewController(animated: true)
    }
    
    func hideVideoMuted() {
        self.remoteVideoView.isHidden = true
        self.localVideoMutedView.isHidden = true
        self.localAudioMutedIndicator.isHidden = true
        self.localVideoMutedIndicator.isHidden = true
        self.remoteAudioMutedIndicator.isHidden = true
    }
    
    func setupButtons() {
        perform(#selector(self.hideControlView), with:nil, afterDelay: 8)
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.viewTapped))
        view.addGestureRecognizer(tapGestureRecognizer)
        view.isUserInteractionEnabled = true
    }
    
    @objc func hideControlView() {
        self.controlView.isHidden = true
    }
    
    @objc func viewTapped() {
        if (self.controlView.isHidden) {
            self.controlView.isHidden = false;
            perform(#selector(self.hideControlView), with: nil, afterDelay: 8)
        } else {
            self.controlView.isHidden = true;
        }
    }
    
    private func resetHideButtonsTimer() {
        TwilioVideoCallVC.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(self.hideControlView), with: nil, afterDelay: 8)
    }
    
    private func onAnswerCall() {
        self.isJoined = true
        self.checkCallTimer.invalidate()
        self.callDeclinedTimer.invalidate()
        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: "video", type: "Answered")
        }
    }
    
    @objc private func setCallTimerToLabel(_ timer: Timer) {
        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)
    }
    
    @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 = self.callAudioPlayer else { return }
            aPlayer.play()
        } catch let error {
            print(error.localizedDescription)
        }
    }
    
    private func stopCallSound() {
        self.callAudioPlayer?.stop()
    }
    
}

// MARK: - Extensions

// MARK: Api Call
extension TwilioVideoCallVC {
    
    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: "video", completionBlock: { (success, sessionError, serverError, error) in
                    if let success = success {
                        Async.main {
                            self.dismissProgressDialog {
                                switch success._callStatusObject {
                                case .answered:
                                    self.invalidateCallTimers()
                                case .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: "video", 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: "video", completionBlock: { (success, sessionError, serverError, error) in
                    if success != nil {
                        Async.main {
                            self.dismissProgressDialog {
                                print("declined")
                                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: "video", 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 TwilioVideoCallVC: 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 TwilioVideoCallVC : 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)")
        
        if (self.remoteVideoView.isHidden) {
            self.userImageView.isHidden = true
            self.remoteVideoView.isHidden = false
        }
        self.setupRemoteVideoView()
        videoTrack.addRenderer(self.remoteView!)
        self.remoteParticipant = participant
        self.onAnswerCall()
    }
    
    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)")
        
        if self.remoteParticipant == participant {
            videoTrack.removeRenderer(self.remoteView!)
            self.closeScreen()
        }
    }
    
    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.audioDevice.isEnabled = true
    }
    
    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
    }
    
    func remoteParticipantDidEnableVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        print("Participant \(participant.identity) enabled video track")
        self.remoteVideoView.isHidden = false
        self.userImageView.isHidden = true
    }
    
    func remoteParticipantDidDisableVideoTrack(participant: RemoteParticipant, publication: RemoteVideoTrackPublication) {
        print("Participant \(participant.identity) disabled video track")
        self.remoteVideoView.isHidden = true
        self.userImageView.isHidden = false
    }
    
    func remoteParticipantDidEnableAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        print("Participant \(participant.identity) enabled audio track")
        self.remoteAudioMutedIndicator.isHidden = true
    }
    
    func remoteParticipantDidDisableAudioTrack(participant: RemoteParticipant, publication: RemoteAudioTrackPublication) {
        print("Participant \(participant.identity) disabled audio track")
        self.remoteAudioMutedIndicator.isHidden = false
    }
    
    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))")
    }
}

// MARK: CameraSourceDelegate Methods
extension TwilioVideoCallVC: CameraSourceDelegate {
    
    func cameraSourceDidFail(source: CameraSource, error: Error) {
        print("Camera source failed with error: \(error.localizedDescription)")
    }
    
}

// MARK: VideoViewDelegate Methods
extension TwilioVideoCallVC: VideoViewDelegate {
    
    func videoViewDimensionsDidChange(view: VideoView, dimensions: CMVideoDimensions) {
        self.view.setNeedsLayout()
    }
    
}
