import Foundation
import Combine
import UserNotifications
#if targetEnvironment(simulator)
#else
import FirebaseMessaging
import FirebaseAuth
import FirebaseFirestore
#endif

extension NotificationType {
    var messagingTopic: String {
        return self.title.lowercased().alphanumeric
    }
    
    static var userDefaultsKey: String {
        return "subscribedTo"
    }
    
    init(string: String) {
        if string == NotificationType.replyToComments.messagingTopic {
            self = .replyToComments
            return
        }
        
        self = .topic(topicSlug: string)
    }
}

extension UserDefaults {
    @objc dynamic var subscribedTo: [String]? {
        stringArray(forKey: NotificationType.userDefaultsKey)
    }
}

protocol NotificationRepository {
    var notificationSettings: PassthroughSubject<UNNotificationSettings, Never> { get }
    var notificationReply: PassthroughSubject<Reply, Never> { get }
    var deeplinkToOpen: PassthroughSubject<Deeplink, Never> { get }
    var token: CurrentValueSubject<String?, Never> { get }
    func updateNotificationSettings() async
    func requestNotificationPermission() async throws
    
    var notificationTypeNames: NSObject.KeyValueObservingPublisher<UserDefaults, [String]?> { get }
    func subscribeToNotificationTypes(_ notificationTypes: [NotificationType])
    func unsubscribeFromNotificationTypes(_ notificationTypes: [NotificationType])
    func savePostcode(_ postcode: String)
}

public class DefaultNotificationRepository: NSObject, NotificationRepository, UNUserNotificationCenterDelegate {
    
    static let standard: DefaultNotificationRepository = {
        DefaultNotificationRepository()
    }()
    
    var notificationSettings: PassthroughSubject<UNNotificationSettings, Never> = .init()
    var notificationReply: PassthroughSubject<Reply, Never> = .init()
    var deeplinkToOpen: PassthroughSubject<Deeplink, Never> = .init()
    var token: CurrentValueSubject<String?, Never> = .init(nil)
    
    var notificationTypeNames = UserDefaults.standard.publisher(for: \.subscribedTo)
    
    override init() {
        super.init()
        
        #if targetEnvironment(simulator)
        DispatchQueue.main.async {
            self.token.send("XXX - FCM Token")
        }
        #else
        Messaging.messaging().token { token, error in
            if let token = token {
                self.token.send(token)
            }
        }
        #endif
        
        let commentAction: UNTextInputNotificationAction
        
        if #available(iOS 15.0, *) {
            commentAction = UNTextInputNotificationAction(identifier: "comment-action",
                                                              title: "Comment",
                                                              options: [],
                                                              icon: nil,
                                                              textInputButtonTitle: "Reply",
                                                              textInputPlaceholder: "Comment…")
        } else {
            commentAction = UNTextInputNotificationAction(identifier: "comment-action",
                                                              title: "Comment",
                                                              options: [],
                                                              textInputButtonTitle: "Reply",
                                                              textInputPlaceholder: "Comment…")
        }

        let category = UNNotificationCategory(identifier: "update-actions",
                                                 actions: [commentAction],
                                       intentIdentifiers: ["INSendMessageIntentIdentifier"], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([category])
        UNUserNotificationCenter.current().delegate = self
    }
    
    func updateNotificationSettings() async {
        let settings = await UNUserNotificationCenter.current().notificationSettings()
        self.notificationSettings.send(settings)
    }

    func requestNotificationPermission() async throws {
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        _ = try await UNUserNotificationCenter.current().requestAuthorization(options: options)
        await updateNotificationSettings()
    }
    
    func subscribeToNotificationTypes(_ notificationTypes: [NotificationType]) {
#if targetEnvironment(simulator)
        for notificationType in notificationTypes {
            addNotificationTypeToUserDefaults(notificationType)
        }
#else
     
        for notificationType in notificationTypes {
            Messaging.messaging().subscribe(toTopic: notificationType.messagingTopic) { error in
                if error == nil {
                    self.addNotificationTypeToUserDefaults(notificationType)
                }
            }
        }
        
        let dataDict = Dictionary(uniqueKeysWithValues: notificationTypes.map { ($0.messagingTopic, true) })
        updateFirebase(dataDict: dataDict)
#endif
    }
    
    private func addNotificationTypeToUserDefaults(_ notificationType: NotificationType) {
        let existing: [String] = UserDefaults.standard.stringArray(forKey: NotificationType.userDefaultsKey) ?? []
        
        UserDefaults.standard.set( (existing + [notificationType.messagingTopic]).unique(), forKey: NotificationType.userDefaultsKey)
    }
    
    func unsubscribeFromNotificationTypes(_ notificationTypes: [NotificationType]) {
#if targetEnvironment(simulator)
        for notificationType in notificationTypes {
            removeNotificationTypeFromUserDefaults(notificationType)
        }
#else
        for notificationType in notificationTypes {
            Messaging.messaging().unsubscribe(fromTopic: notificationType.messagingTopic) { error in
                if error == nil {
                    self.removeNotificationTypeFromUserDefaults(notificationType)
                }
            }
            
            let dataDict = Dictionary(uniqueKeysWithValues: notificationTypes.map { ($0.messagingTopic, false) })
            updateFirebase(dataDict: dataDict)
        }
#endif
    }
    
    private func removeNotificationTypeFromUserDefaults(_ notificationType: NotificationType) {
        var existing: [String] = UserDefaults.standard.stringArray(forKey: NotificationType.userDefaultsKey) ?? []
        existing.removeAll(where: { $0 == notificationType.messagingTopic })
        UserDefaults.standard.set(existing, forKey: NotificationType.userDefaultsKey)
    }
    
    public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        
        let userInfo = response.notification.request.content.userInfo
        if
            let urlString = userInfo["url"] as? String,
            let url = URL(string: urlString),
            let deeplink = Deeplink(url: url) {
            deeplinkToOpen.send(deeplink)
        }
        
        if response.actionIdentifier == "comment-action" {
            if let response = response as? UNTextInputNotificationResponse {
                let userInfo = response.notification.request.content.userInfo
                if let postIdString = userInfo["postId"] as? String,
                   let commentIdString = userInfo["commentId"] as? String,
                   let postId = Int(postIdString),
                   let commentId = Int(commentIdString) {
                    let reply = Reply(content: response.userText, post: postId, parent: commentId)
                    self.notificationReply.send(reply)
                }
            }
        }
        completionHandler()
    }
    
    func savePostcode(_ postcode: String) {
#if targetEnvironment(simulator)
#else
        let dataDict = ["postcode": postcode]
        updateFirebase(dataDict: dataDict)
#endif
    }
    
    private func updateFirebase(dataDict: [String: AnyHashable]) {
#if targetEnvironment(simulator)
#else
        Task {
            let firebaseAuthResponse = try await Auth.auth().signInAnonymously()
            let user = firebaseAuthResponse.user
            let db = Firestore.firestore()
            let userDocumentRef = db.collection("users").document(user.uid)
            
            let _ = try await userDocumentRef.setData(dataDict, merge: true)
        }
#endif
    }
}
