import AVFoundation
import FLAnimatedImage
import MobileCoreServices
import Photos
import PhotosUI
import UIKit

protocol PhotoSelectionControllerDelegate: class {
    
    func photoSelectionCanceled()
    
    func didFailWithError(_ error: String)
    func didFailToGetPermission(_ message: String)
    
    func didUserSelectImage(_ image: SelectedImage)
    func didUserSelectAnimatedImage(_ image: AnimatedImage)
    
    func didPhotoSelectionCompleted()
}

typealias FileData = (data: Data, type: CacheFileType)
typealias SelectedImage = (image: UIImage, type: CacheFileType)
typealias AnimatedImage = (image: FLAnimatedImage, type: CacheFileType)

typealias ImageFetchClosure = (_ image: UIImage?) -> Void

typealias PhotoAuthStatus = (isAllowed: Bool, isLimited: Bool)

final class PhotoSelectionController: NSObject, PhotoSelectionControllerProtocol {
    
    weak var delegate: PhotoSelectionControllerDelegate?
    
    var phManager: PHImageManager = PHImageManager.default()
    var library: PHPhotoLibrary = PHPhotoLibrary.shared()
    
    // MARK: - Life cycle
    deinit {
        if #available(iOS 13, *) {
            library.unregisterAvailabilityObserver(self)
        }
        library.unregisterChangeObserver(self)
    }
    
    override init() {
        super.init()
        if #available(iOS 13, *) {
            library.register(self as PHPhotoLibraryAvailabilityObserver)
        }
        library.register(self as PHPhotoLibraryChangeObserver)
    }
    
    func checkCameraAccess(at vc: UIViewController, completion: @escaping TypeClosure<Bool>) {
        guard AVCaptureDevice.default(for: .video) != nil else {
            delegate?.didFailWithError(Localizable.errorCameraNotAvailable())
            completion(false)
            return
        }
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .denied, .restricted:
            delegate?.didFailToGetPermission(Localizable.accessErrorCamera())
            completion(false)
        case .authorized:
            completion(true)
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { [weak self] success in
                if !success {
                    self?.delegate?.didFailToGetPermission(Localizable.accessErrorCamera())
                }
                completion(success)
            }
            
        @unknown default:
            break
        }
    }
    
    func checkPhotosAccess(at vc: UIViewController, completion: @escaping TypeClosure<PhotoAuthStatus>) {
        
        let status: PHAuthorizationStatus
        
        if #available(iOS 14.0, *) {
            status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
        } else {
            status = PHPhotoLibrary.authorizationStatus()
        }
        
        switch status {
        case .authorized:
            
            completion((isAllowed: true, isLimited: false))
            
        case .limited:
            
            if #available(iOS 14.0, *) {
                
                completion((isAllowed: true, isLimited: true))
                
            } else {
                
                completion((isAllowed: true, isLimited: true))
            }
            
             completion((isAllowed: true, isLimited: true))
            
        case .denied, .restricted:
            
            delegate?.didFailToGetPermission(Localizable.accessErrorPhotos())
            completion((isAllowed: false, isLimited: false))
            
        case .notDetermined:
            
            requestPhotoLibraryAuthorization(at: vc, completion: completion)
            
        @unknown default:
            break
        }
    }
    
    func requestPhotoLibraryAuthorization(at vc: UIViewController, completion: @escaping TypeClosure<PhotoAuthStatus>) {
        
        if #available(iOS 14.0, *) {
            
            PHPhotoLibrary.requestAuthorization(for: .readWrite) { [weak self] status in
                
                self?.handlePhotoLibraryAuthorizationStatus(at: vc, status: status, completion: completion)
                
            }
            
        } else {
            
            PHPhotoLibrary.requestAuthorization { [weak self] status in
                
                self?.handlePhotoLibraryAuthorizationStatus(at: vc, status: status, completion: completion)
                
            }
            
        }
        
    }
    
    private func handlePhotoLibraryAuthorizationStatus(at vc: UIViewController,
                                                       status: PHAuthorizationStatus,
                                                       completion: @escaping TypeClosure<PhotoAuthStatus>) {
        
        switch status {
        case .authorized:
            completion((isAllowed: true, isLimited: false))
            
        case .limited:
            
            if #available(iOS 14.0, *) {
                
                completion((isAllowed: true, isLimited: true))
                
            } else {
                
                completion((isAllowed: true, isLimited: true))
            }
            
        case .denied, .restricted:
            delegate?.didFailToGetPermission(Localizable.accessErrorPhotos())
            completion((isAllowed: false, isLimited: false))
            
        case .notDetermined:
            break // won't happen but still
        @unknown default:
            break
        }
    }
    
}

// MARK: - PHPhotoLibraryChangeObserver
extension PhotoSelectionController: PHPhotoLibraryChangeObserver {
    
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        // NOTE: - For cases while you present custom UI - trigger updates methods from here
    }
    
}

// MARK: - PHPhotoLibraryAvailabilityObserver
extension PhotoSelectionController: PHPhotoLibraryAvailabilityObserver {
    
    @available(iOS 13, *)
    func photoLibraryDidBecomeUnavailable(_ photoLibrary: PHPhotoLibrary) {
        // NOTE: - For cases while you present custom UI - trigger updates methods from here
    }
    
}

// MARK: - Image picker
extension PhotoSelectionController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        
        picker.dismiss(animated: true, completion: nil)
        delegate?.photoSelectionCanceled()
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
        
        // NOTE: image captured by device camera do not have image URL
        guard let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL else {
            handleCapturedImage(picker, info: info, imageType: .jpeg)
            return
        }
        
        guard let type = CacheFileType(rawValue: imageURL.pathExtension) else {
            let message: String = "Unknown file type value"
            LoggerService.logErrorWithTrace(message)
            return
        }
        handleCapturedImage(picker, info: info, imageType: type)
    }
    
    private func handleCapturedImage(_ picker: UIImagePickerController,
                                     info: [UIImagePickerController.InfoKey: Any],
                                     imageType: CacheFileType) {
        
        var selectedImage: UIImage?
        let editedImage = info[.editedImage] as? UIImage
        
        if editedImage?.jpegData(compressionQuality: 1.0) == nil {
            // Image was NOT edited
            guard let image = info[.originalImage] as? UIImage else {
                let message: String = "Unable to get image"
                LoggerService.logErrorWithTrace(message)
                return
            }
            selectedImage = image
        } else {
            // Image was edited
            guard let image = info[.editedImage] as? UIImage else {
                let message: String = "Unable to get image"
                LoggerService.logErrorWithTrace(message)
                return
            }
            selectedImage = image
        }
        guard let image = selectedImage else {
            let message: String = "Image wasn't provided"
            LoggerService.logErrorWithTrace(message)
            return
        }
        guard imageType == .gif else {
            delegate?.didUserSelectImage(SelectedImage(image: image, type: imageType))
            picker.dismiss(animated: true) { [weak self] in
                self?.delegate?.didPhotoSelectionCompleted()
            }
            return
        }
        
        guard let imgUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else {
            let message: String = "Unable to get image asset"
            LoggerService.logErrorWithTrace(message)
            return
        }
        do {
            let data = try Data(contentsOf: imgUrl)
            guard let animImage = FLAnimatedImage(animatedGIFData: data) else {
                let message: String = "Unable to create animated image"
                LoggerService.logErrorWithTrace(message)
                return
            }
            
            delegate?.didUserSelectAnimatedImage(AnimatedImage(image: animImage, type: imageType))
            
            picker.dismiss(animated: true) { [weak self] in
                self?.delegate?.didPhotoSelectionCompleted()
            }
        } catch {
            let message: String = error.localizedDescription
            LoggerService.logErrorWithTrace(message)
        }
        
    }
    
    private func requestGIFData(_ asset: PHAsset, completion: @escaping ImageFetchClosure) {
        
        let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = false
        options.isSynchronous = true
        options.resizeMode = .exact
        options.deliveryMode = .highQualityFormat
        options.version = .original
        
        phManager.requestImageData(for: asset, options: options, resultHandler: { imageData, UTI, _, _ in
            
            guard let uti = UTI else {
                let message: String = "Unable to get image UTI"
                LoggerService.logErrorWithTrace(message)
                return
            }
            let isGif = UTTypeConformsTo(uti as CFString, kUTTypeGIF)
            guard let data = imageData, isGif else{
                let message: String = "Unable to get GIF image"
                LoggerService.logErrorWithTrace(message)
                return
            }
            let image = UIImage(data: data)
            completion(image)
        })
    }
    
}

// MARK: - PHPickerViewControllerDelegate
extension PhotoSelectionController: PHPickerViewControllerDelegate {
    
    // NOTE: - Single photo selection handling
    @available(iOS 14, *)
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        
        picker.dismiss(animated: true, completion: { [weak self] in
            
            self?.executeOnMain {
                
                let data: DataUpdateInfo = ModuleTypeDeinitMessage(type: PHPickerViewController.self).toDataUpdate()
                AppDelegate.shared.notifyObservers(about: .moduleTypeDeinit, with: data)
            }
        })
        
        // Empty results - user canceled photo selection
        guard results.isEmpty else {
            
            guard let itemProvider = results.first?.itemProvider else {
            
            let error: String = Localizable.imageErrorUnableToGetFile()
            self.failedToLoadImage(with: error)
            return
        }
        
        guard itemProvider.canLoadObject(ofClass: UIImage.self) else {
            
            let error: String = Localizable.imageErrorUnableToGetFile()
            self.failedToLoadImage(with: error)
            return
        }
        
        let _ = itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
            
            guard let err = error else {
                
                guard let img = image as? UIImage else {
                    
                    let error: String = Localizable.imageErrorUnableToGetFile()
                    self?.failedToLoadImage(with: error)
                    return
                }
                
                let selectedImage: SelectedImage = (image: img, type: .png)
                
                self?.executeOnMain { [weak self] in
                    self?.delegate?.didUserSelectImage(selectedImage)
                    self?.delegate?.didPhotoSelectionCompleted()
                }
                
                return
            }
            
            self?.failedToLoadImage(with: err.localizedDescription)
        }
            
            return   
        }
        
        delegate?.photoSelectionCanceled()
        
    }
    
    private func failedToLoadImage(with error: String) {
        
        executeOnMain { [weak self] in
            self?.delegate?.didFailWithError(error)
            self?.delegate?.photoSelectionCanceled()
        }
    }
    
}