import Foundation
import UIKit
import UserNotifications

/// Captures this phone's APNS device token so it can ride along in the sync `Hello` (SYNC §6) and,
/// once the relay ships, let the host wake the phone for an approval (docs/CLOUD_INFRA.md §3).
///
/// Best-effort: the token may be nil until APNS responds, in which case it's simply included on a
/// later (re)connect — the phone still works fully on LAN without it.
@MainActor
final class PushRegistrar {
    static let shared = PushRegistrar()
    private init() {}

    /// Hex-encoded APNS device token, or nil until registration completes.
    private(set) var tokenHex: String?

    /// Surface the notifications prompt and, if granted, register for remote notifications. Safe to
    /// call on every launch; the system de-dupes registration.
    func requestAuthorizationAndRegister() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
            guard granted else { return }
            Task { @MainActor in UIApplication.shared.registerForRemoteNotifications() }
        }
    }

    func didRegister(deviceToken: Data) {
        tokenHex = deviceToken.map { String(format: "%02x", $0) }.joined()
    }
}

/// SwiftUI needs a UIKit app delegate to receive the remote-registration callbacks; wired via
/// `@UIApplicationDelegateAdaptor` in `NucleicRemoteApp`.
final class PushAppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Task { @MainActor in PushRegistrar.shared.didRegister(deviceToken: deviceToken) }
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        // Best-effort: no token → the phone still works on LAN, just no remote wake.
    }

    /// Silent pushes (content-available). Three kinds route here:
    /// - `clear`: recall the "waiting for your approval" tickle once the approval is resolved on
    ///   another device — wake briefly to withdraw the stale notification.
    /// - `adopt`: a Mac push-started a Live Activity while the app was closed; come online long
    ///   enough to harvest and register that activity's update token so live updates + a clean end
    ///   resume without the user opening the app (UX_IOS §5.3). This one holds the completion handler
    ///   until the token is registered (or the background window closes), so the process isn't
    ///   suspended mid-connect.
    /// - `reconnect`: the Mac noticed our LAN link drop and is nudging us to re-dial over a non-local
    ///   transport (tailnet/relay). Come online long enough to re-run the candidate loop so the
    ///   connection follows the phone off-LAN without the user opening the app (SYNC_PROTOCOL §3.2).
    /// Other pushes (e.g. the approval wake tickle, which is an alert) don't route here.
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        if userInfo["adopt"] != nil {
            // The store manages its own background-task assertion for this; report .newData once it
            // has brought the connection up and registered the token (or given up).
            Task { @MainActor in
                RemoteStore.shared.handleLiveActivityAdoptWake { completionHandler(.newData) }
            }
            return
        }
        if userInfo["reconnect"] != nil {
            // The store holds its own background-task assertion while it re-dials; report .newData
            // once a link is back up (or the background window closed).
            Task { @MainActor in
                RemoteStore.shared.handleReconnectWake { completionHandler(.newData) }
            }
            return
        }
        if userInfo["clear"] != nil {
            // Fire-and-forget on the main actor (NotificationRouter is @MainActor); the removal is a
            // quick UNUserNotificationCenter call, so we can report .noData right away.
            Task { @MainActor in NotificationRouter.shared.withdrawApprovalAttention() }
        }
        completionHandler(.noData)
    }
}
