import UserNotifications
import WMF

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?
    
    private lazy var apiController: RemoteNotificationsAPIController = {
        let configuration = Configuration.current
        let session = Session(configuration: configuration)
        let controller = RemoteNotificationsAPIController(session: session, configuration: configuration)
        return controller
    }()
    private let sharedCache = SharedContainerCache<PushNotificationsCache>.init(fileName: SharedContainerCacheCommonNames.pushNotificationsCache, defaultCache: { PushNotificationsCache(settings: .default, notifications: []) })
    
    private let fallbackPushContent = WMFLocalizedString("notifications-push-fallback-body-text", value: "New activity on Wikipedia", comment: "Fallback body content of a push notification whose content cannot be determined. Could be either due multiple notifications represented or errors.")

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        
        guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
            contentHandler(request.content)
            return
        }
        
        self.bestAttemptContent = bestAttemptContent
        
        guard bestAttemptContent.body == EchoModelVersion.current else {
            bestAttemptContent.body = fallbackPushContent
            contentHandler(bestAttemptContent)
            return
        }
        
        let cache = sharedCache.loadCache()
        let project = WikimediaProject.wikipedia(cache.settings.primaryLanguageCode, cache.settings.primaryLocalizedName, nil)
        
        let fallbackPushContent = self.fallbackPushContent
        
        apiController.getUnreadPushNotifications(from: project) { [weak self] fetchedNotifications, error in
            
            DispatchQueue.main.async {
                guard let self = self,
                      error == nil else {
                    bestAttemptContent.body = fallbackPushContent
                    contentHandler(bestAttemptContent)
                    return
                }
                
                let finalNotifications = NotificationServiceHelper.determineNotificationsToDisplayAndCache(fetchedNotifications: fetchedNotifications, cachedNotifications: cache.notifications)
                let finalNotificationsToDisplay = finalNotifications.notificationsToDisplay
                let finalNotificationsToCache = finalNotifications.notificationsToCache
                
                var newCache = cache
                newCache.notifications = finalNotificationsToCache
                self.sharedCache.saveCache(newCache)
                
                // specific handling for talk page types (New messages title, bundled body)
                if let talkPageContent = NotificationServiceHelper.talkPageContent(for: finalNotificationsToDisplay) {
                    bestAttemptContent.subtitle = talkPageContent.subtitle
                    bestAttemptContent.body = talkPageContent.body
                } else if finalNotificationsToDisplay.count == 1,
                       let pushContentText = finalNotificationsToDisplay.first?.pushContentText {
                    bestAttemptContent.body = pushContentText
                } else {
                    bestAttemptContent.body = fallbackPushContent
                }

                // Assigning interruption level and relevance score only available starting on iOS 15
                if #available(iOS 15.0, *) {
                    if finalNotifications.notificationsToDisplay.count == 1, let notification = finalNotifications.notificationsToDisplay.first {
                        let priority = RemoteNotification.typeFrom(notification: notification).priority
                        bestAttemptContent.interruptionLevel = priority.interruptionLevel
                        bestAttemptContent.relevanceScore = priority.relevanceScore
                    } else {
                        if NotificationServiceHelper.allNotificationsAreForSameTalkPage(notifications: finalNotificationsToDisplay) {
                            bestAttemptContent.interruptionLevel = RemoteNotificationType.mentionInTalkPage.priority.interruptionLevel
                            bestAttemptContent.relevanceScore = RemoteNotificationType.mentionInTalkPage.priority.relevanceScore
                        } else {
                            bestAttemptContent.interruptionLevel = RemoteNotificationType.bulkPriority.interruptionLevel
                            bestAttemptContent.relevanceScore = RemoteNotificationType.bulkPriority.relevanceScore
                        }
                    }
                }

                let displayContentIdentifiers = finalNotificationsToDisplay.compactMap { PushNotificationContentIdentifier(key: $0.key, date: $0.date) }
                PushNotificationContentIdentifier.save(displayContentIdentifiers, to: &bestAttemptContent.userInfo)

                bestAttemptContent.badge = NSNumber(value: newCache.currentUnreadCount + finalNotificationsToDisplay.count)
                
                contentHandler(bestAttemptContent)
            }
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler,
            let bestAttemptContent = bestAttemptContent {
            bestAttemptContent.body = fallbackPushContent
            contentHandler(bestAttemptContent)
        }
    }
}
