import Capacitor
import UserNotifications

public class LocalNotificationsHandler: NSObject, NotificationHandlerProtocol {

    public weak var plugin: CAPPlugin?

    // Local list of notification id -> JSObject for storing options
    // between notification requets
    var notificationRequestLookup = [String: JSObject]()

    public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) {
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
            completion?(granted, error)
        }
    }

    public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) {
        let center = UNUserNotificationCenter.current()
        center.getNotificationSettings { settings in
            completion?(settings.authorizationStatus)
        }
    }

    public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions {
        let notificationData = makeNotificationRequestJSObject(notification.request)

        self.plugin?.notifyListeners("localNotificationReceived", data: notificationData)

        if let options = notificationRequestLookup[notification.request.identifier] {
            // `foreground` takes precedence over `silent` when both are provided.
            if let foreground = options["foreground"] as? Bool {
                if foreground {
                    return [.badge, .sound, .banner, .list]
                }
                return UNNotificationPresentationOptions.init(rawValue: 0)
            }
            let silent = options["silent"] as? Bool ?? false
            if silent {
                return UNNotificationPresentationOptions.init(rawValue: 0)
            }
        }

        if let optionsArray = self.plugin?.getConfig().getArray("presentationOptions") as? [String] {
            var presentationOptions = UNNotificationPresentationOptions.init()

            optionsArray.forEach { option in
                switch option {
                case "banner":
                    presentationOptions.insert(.banner)
                case "list":
                    presentationOptions.insert(.list)
                case "badge":
                    presentationOptions.insert(.badge)
                case "sound":
                    presentationOptions.insert(.sound)
                default:
                    print("Unrecognized presentation option: \(option)")
                }
            }

            return presentationOptions
        }

        return [
            .badge,
            .sound,
            .banner,
            .list
        ]
    }

    public func didReceive(response: UNNotificationResponse) {
        var data = JSObject()

        // Get the info for the original notification request
        let originalNotificationRequest = response.notification.request

        let actionId = response.actionIdentifier

        // We turn the two default actions (open/dismiss) into generic strings
        if actionId == UNNotificationDefaultActionIdentifier {
            data["actionId"] = "tap"
        } else if actionId == UNNotificationDismissActionIdentifier {
            data["actionId"] = "dismiss"
        } else {
            data["actionId"] = actionId
        }

        // If the type of action was for an input type, get the value
        if let inputType = response as? UNTextInputNotificationResponse {
            data["inputValue"] = inputType.userText
        }

        data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest)

        self.plugin?.notifyListeners("localNotificationActionPerformed", data: data, retainUntilConsumed: true)
    }

    /**
     * Turn a UNNotificationRequest into a JSObject to return back to the client.
     */
    func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
        let notificationRequest = notificationRequestLookup[request.identifier] ?? [:]
        var notification = makePendingNotificationRequestJSObject(request)
        notification["sound"] = notificationRequest["sound"]  ?? ""
        notification["actionTypeId"] = request.content.categoryIdentifier
        notification["attachments"] = notificationRequest["attachments"]  ?? []
        if let badge = request.content.badge {
            notification["badge"] = badge.intValue
        }
        if let foreground = notificationRequest["foreground"] as? Bool {
            notification["foreground"] = foreground
        }
        return notification

    }

    func makePendingNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
        var notification: JSObject = [
            "id": Int(request.identifier) ?? -1,
            "title": request.content.title,
            "body": request.content.body
        ]

        if let userInfo = JSTypes.coerceDictionaryToJSObject(request.content.userInfo) {
            // `extra` is documented as `any`, not just an object. Only dictionaries
            // need per-key Date normalization; anything else is passed through
            // as-is rather than substituting the whole userInfo object.
            if var extraDict = userInfo["cap_extra"] as? JSObject {
                for(key, value) in extraDict {
                    if let date = value as? Date {
                        let dateString = ISO8601DateFormatter().string(from: date)
                        extraDict[key] = dateString
                    }
                }
                notification["extra"] = extraDict
            } else if let extraValue = userInfo["cap_extra"] {
                notification["extra"] = extraValue
            }

            if var schedule = userInfo["cap_schedule"] as? JSObject {
                // convert schedule at date to string
                if let date = schedule["at"] as? Date {
                    let dateString = ISO8601DateFormatter().string(from: date)
                    schedule["at"] = dateString
                }

                notification["schedule"] = schedule
            }
        }

        return notification

    }
}
