import APIClient
import Foundation
import HostRegistry
import OSLog
import UIKit
import UserNotifications
import WireProtocol

/// T-iOS-21 · APNs 注册侧：授权 → registerForRemoteNotifications → device
/// token 对每个已配对主机 `POST /push/apns-token`（builder 归 T-iOS-38）。
///
/// 授权决策（任务要求"document the choice"）：请求**真授权 [.alert, .sound]**
/// 而非 provisional —— provisional 只静默投递到通知中心（无锁屏横幅、无声音、
/// 无可交互动作），而本任务的核心是锁屏 Allow/Deny 两次手势闭环，必须 alert
/// 级展示（服务器 gate 推送也带 `sound: 'default'`，src/push/apns.ts:342）。
///
/// 重试语义：注册失败只记日志绝不 crash；device token 每次
/// `registerForRemoteNotifications` 都会经 delegate 重新送达（launch/scene
/// 激活各触发一次 `activate()`），`registeredHostIds` 记账保证只补注册失败/
/// 新增的主机（服务器端 upsert 本身幂等，5/min/IP 限频下不浪费配额）。

// MARK: - Seams（UNUserNotificationCenter / UIApplication 不可在单测实例化流程）

/// Seam over the `UNUserNotificationCenter` surface this feature touches.
@MainActor
protocol NotificationCenterClient: AnyObject {
    func authorizationStatus() async -> UNAuthorizationStatus
    func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool
    func setNotificationCategories(_ categories: Set<UNNotificationCategory>)
    func add(_ request: UNNotificationRequest) async throws
}

@MainActor
final class UNNotificationCenterAdapter: NotificationCenterClient {
    private let center = UNUserNotificationCenter.current()

    func authorizationStatus() async -> UNAuthorizationStatus {
        // The async `notificationSettings()` returns non-Sendable
        // UNNotificationSettings across an isolation hop (Swift 6 error);
        // extract only the Sendable status inside the completion instead.
        // @Sendable literal: the closure must NOT inherit this class's
        // MainActor isolation — UNUserNotificationCenter invokes it on a
        // background queue, and an isolated closure traps the Swift 6 runtime
        // executor check (dispatch_assert_queue_fail; W7 verify boot-crash).
        await withCheckedContinuation { continuation in
            center.getNotificationSettings { @Sendable settings in
                continuation.resume(returning: settings.authorizationStatus)
            }
        }
    }

    func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool {
        // Completion-handler bridge for the same Swift 6 reason as above (the
        // SDK's async variant sends the non-Sendable center across isolation).
        try await withCheckedThrowingContinuation { continuation in
            center.requestAuthorization(options: options) { @Sendable granted, error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: granted)
                }
            }
        }
    }

    func setNotificationCategories(_ categories: Set<UNNotificationCategory>) {
        center.setNotificationCategories(categories)
    }

    func add(_ request: UNNotificationRequest) async throws {
        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, any Error>) in
            center.add(request) { @Sendable error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: ())
                }
            }
        }
    }
}

/// Seam over `UIApplication.registerForRemoteNotifications`.
@MainActor
protocol RemoteNotificationRegistering: AnyObject {
    func registerForRemoteNotifications()
}

@MainActor
final class UIApplicationRemoteRegistrar: RemoteNotificationRegistering {
    func registerForRemoteNotifications() {
        UIApplication.shared.registerForRemoteNotifications()
    }
}

// MARK: - WEBTERM_GATE category（安全注的单一定义点）

/// 锁屏 Allow/Deny 的 category 注册形状。**安全注（plan T-iOS-21）**：
/// - Allow 必带 `.authenticationRequired` —— 锁屏批准 = 授权主机执行命令，
///   必须 Face ID/通行码确认；
/// - Deny 保持免认证（fail-safe：旁观者拿到锁屏手机只能拒绝）；
/// - 两动作都**不带** `.foreground` —— 决策在后台 POST，不拉起 UI。
enum GateNotificationCategory {
    /// 与服务器 `GATE_CATEGORY` 一致（src/push/apns.ts:52，T-iOS-20 定稿）。
    static let identifier = "WEBTERM_GATE"
    static let allowActionId = "WEBTERM_ALLOW"
    static let denyActionId = "WEBTERM_DENY"
    static let allowTitle = "允许"
    static let denyTitle = "拒绝"

    static func category() -> UNNotificationCategory {
        let allow = UNNotificationAction(
            identifier: allowActionId, title: allowTitle,
            options: [.authenticationRequired]
        )
        let deny = UNNotificationAction(
            identifier: denyActionId, title: denyTitle,
            options: [.destructive] // 红色渲染，纯视觉；不含 .foreground/.authenticationRequired
        )
        return UNNotificationCategory(
            identifier: identifier, actions: [allow, deny],
            intentIdentifiers: [], options: []
        )
    }
}

// MARK: - PushRegistrar

@MainActor
final class PushRegistrar {
    /// 真授权（非 provisional）——理由见文件头 doc。
    static let authorizationOptions: UNAuthorizationOptions = [.alert, .sound]

    private let hostStore: any HostStore
    private let http: any HTTPTransport
    private let center: any NotificationCenterClient
    private let remote: any RemoteNotificationRegistering
    private let logger = Logger(subsystem: PushLog.subsystem, category: PushLog.registrar)

    /// 当前 device token 的小写 hex；仅驻内存（iOS 每次注册都重新送达，
    /// 无需落盘——落盘的只有服务器侧的注册表）。
    private(set) var currentTokenHex: String?
    /// 已用 `currentTokenHex` 注册成功的主机（重试只补缺）。
    private var registeredHostIds: Set<UUID> = []

    init(
        hostStore: any HostStore,
        http: any HTTPTransport,
        center: any NotificationCenterClient,
        remote: any RemoteNotificationRegistering
    ) {
        self.hostStore = hostStore
        self.http = http
        self.center = center
        self.remote = remote
    }

    /// 幂等激活：每次 launch / scene 转 active 调一次。category 注册无条件；
    /// 授权与远程注册只在**已有配对主机**时进行（无主机 = 无推送来源，不打扰;
    /// 配对后的下一次前台激活/启动会补上）。
    func activate() async {
        center.setNotificationCategories([GateNotificationCategory.category()])
        let hosts: [HostRegistry.Host]
        do {
            hosts = try await hostStore.loadAll()
        } catch {
            logger.error("push activate: host store read failed: \(error)")
            return
        }
        guard !hosts.isEmpty else {
            logger.debug("push activate: no paired hosts — skip authorization")
            return
        }
        guard await ensureAuthorization() else { return }
        remote.registerForRemoteNotifications()
    }

    /// `didRegisterForRemoteNotificationsWithDeviceToken` 入口。
    func handleDeviceToken(_ deviceToken: Data) async {
        let hex = Self.hexToken(deviceToken)
        if hex != currentTokenHex {
            currentTokenHex = hex
            registeredHostIds = []
        }
        await registerPendingHosts()
    }

    /// `didFailToRegisterForRemoteNotificationsWithError` 入口：只记日志
    /// （模拟器/无 aps-environment entitlement 的构建走到这里，绝不 crash）。
    func handleRegistrationFailure(_ error: any Error) {
        logger.error("remote notification registration failed: \(error)")
    }

    /// 主机移除时注销该主机上的 device token（**additive hook**：当前 App
    /// 层尚无移除主机的 UI 路径——`HostStore.remove(id:)` 无消费者；未来的
    /// 移除路径应调用本方法。失败仅记日志：服务器侧对失效 token 也会经
    /// APNs 410 自行清理）。
    func handleHostRemoved(_ host: HostRegistry.Host) async {
        registeredHostIds.remove(host.id)
        guard let token = currentTokenHex else { return }
        do {
            try await APIClient(endpoint: host.endpoint, http: http)
                .unregisterApnsToken(token)
        } catch {
            logger.error("APNs token unregister failed for host \(host.id): \(error)")
        }
    }

    // MARK: - Internals

    /// notDetermined → 弹真授权请求；denied → 短路；其余（authorized/
    /// provisional/ephemeral）→ 直接放行。
    private func ensureAuthorization() async -> Bool {
        switch await center.authorizationStatus() {
        case .notDetermined:
            do {
                guard try await center.requestAuthorization(
                    options: Self.authorizationOptions
                ) else {
                    logger.notice("push authorization denied by user")
                    return false
                }
                return true
            } catch {
                logger.error("push authorization request failed: \(error)")
                return false
            }
        case .denied:
            logger.notice("push authorization previously denied — skip registration")
            return false
        default:
            return true
        }
    }

    private func registerPendingHosts() async {
        guard let token = currentTokenHex else { return }
        let hosts: [HostRegistry.Host]
        do {
            hosts = try await hostStore.loadAll()
        } catch {
            logger.error("push token registration: host store read failed: \(error)")
            return
        }
        for host in hosts where !registeredHostIds.contains(host.id) {
            do {
                try await APIClient(endpoint: host.endpoint, http: http)
                    .registerApnsToken(token)
                registeredHostIds.insert(host.id)
            } catch {
                // 失败续命：不 crash、不中断其余主机；下一次 token 送达
                //（下次启动/前台激活）自动重试本主机。
                logger.error("APNs token registration failed for host \(host.id): \(error)")
            }
        }
    }

    /// APNs device token → 小写 hex（服务器 wire 形状 64–160 hex，T-iOS-38）。
    static func hexToken(_ data: Data) -> String {
        data.map { String(format: "%02x", $0) }.joined()
    }
}

// MARK: - PushAppDelegate（app 生命周期胶水，单测不覆盖——全部逻辑在上方可测类型里）

/// `@UIApplicationDelegateAdaptor` 入口：remote-notification 回调只能经
/// UIApplicationDelegate 送达，SwiftUI App 本体拿不到。
///
/// 接线路径（wiring 决策，任务允许的最小增量）：SwiftUI 生命周期先跑
/// `WebTermApp.init`（在那里创建 coordinator 并静态交接到 `bootstrap`），后跑
/// `didFinishLaunching`（在此消费、组装 registrar/handler 并把 handler 设为
/// UNUserNotificationCenter delegate——Apple 要求该 delegate 必须在启动完成前
/// 就位，否则冷启动的通知动作会丢）。激活（授权+注册）不在这里做，而是随
/// scenePhase 转 active（每次启动都会发生一次）由 `WebTermApp` 触发——后台
/// 拉起（锁屏 Allow/Deny）时场景不会激活，正好省掉 30 秒窗口里的注册流量。
@MainActor
final class PushAppDelegate: NSObject, UIApplicationDelegate {
    /// WebTermApp.init → didFinishLaunching 的一次性交接槽。
    static var bootstrap: AppCoordinator?

    /// 单测宿主（XCTest 已加载）与 XCUITest 拉起（环境带会话标识）都跳过
    /// 推送接线：授权弹窗会污染确定性测试；测试用注入替身直接驱动逻辑。
    static var isRunningUnderTests: Bool {
        NSClassFromString("XCTestCase") != nil
            || ProcessInfo.processInfo.environment["XCTestSessionIdentifier"] != nil
    }

    private(set) var registrar: PushRegistrar?
    private(set) var actionHandler: NotificationActionHandler?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        guard !Self.isRunningUnderTests, let coordinator = Self.bootstrap else {
            Self.bootstrap = nil
            return true
        }
        Self.bootstrap = nil
        let wiring = coordinator.makePushWiring()
        registrar = wiring.registrar
        actionHandler = wiring.handler
        UNUserNotificationCenter.current().delegate = wiring.handler
        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Task { await registrar?.handleDeviceToken(deviceToken) }
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: any Error
    ) {
        registrar?.handleRegistrationFailure(error)
    }

    /// scenePhase 转 active（含每次启动）→ 幂等激活。
    func activatePush() {
        Task { await registrar?.activate() }
    }
}

enum PushLog {
    static let subsystem = "com.yaojia.webterm"
    static let registrar = "push-registrar"
    static let actionHandler = "push-action"
}
