// Sources/Core/Push/DeviceTokenRegistrar.swift
// T-2.9: APNs — device token storage + deferred sidecar registration

import UIKit

/// Receives the APNs device token and registers it with the paired sidecar.
///
/// **Phase 2 note:** the sidecar does not yet expose a dedicated
/// `/device-token` endpoint. The token and a *pending* flag are stored in
/// `UserDefaults`; `registerWithSidecar` will be fully wired once the
/// endpoint lands in a follow-up task. Until then the method records the
/// intent and returns without making a network call.
actor DeviceTokenRegistrar {

    // MARK: - Singleton

    static let shared = DeviceTokenRegistrar()

    private init() {}

    // MARK: - UserDefaults keys

    private enum Keys {
        static let tokenHex        = "piremote.push.tokenHex"
        static let registrationPending = "piremote.push.registrationPending"
    }

    // MARK: - State

    /// Hex-encoded 40-byte device token string (80 hex chars).
    private(set) var tokenHex: String? = nil

    // MARK: - Token ingestion

    /// Store the raw token data received from `didRegisterForRemoteNotificationsWithDeviceToken`.
    func didRegister(tokenData: Data) {
        let hex = tokenData.map { String(format: "%02x", $0) }.joined()
        tokenHex = hex
        UserDefaults.standard.set(hex, forKey: Keys.tokenHex)
        // Mark any previously-pending registration as still pending so it is
        // retried when `registerWithSidecar` is next called.
        if UserDefaults.standard.bool(forKey: Keys.registrationPending) == false {
            UserDefaults.standard.set(true, forKey: Keys.registrationPending)
        }
    }

    // MARK: - Registration

    /// Attempt to register the current device token with the paired sidecar.
    ///
    /// **Current behaviour (stub):** the sidecar endpoint is not yet
    /// available, so the method persists the token locally and marks the
    /// registration as pending. The pending flag will be consumed and cleared
    /// when the real endpoint is wired in the Phase 2 follow-up.
    ///
    /// - Parameter credential: The active `SidecarCredential` obtained from
    ///   the Keychain after a successful pairing.
    /// - Throws: `DeviceTokenRegistrarError.noTokenAvailable` if the APNs
    ///   token has not been received yet.
    func registerWithSidecar(credential: SidecarCredential) async throws {
        // Recover a previously-stored token if we don't have one in memory yet
        // (e.g. after a cold launch where `didRegister` fires after we call
        // `registerWithSidecar`).
        if tokenHex == nil {
            tokenHex = UserDefaults.standard.string(forKey: Keys.tokenHex)
        }

        guard tokenHex != nil else {
            throw DeviceTokenRegistrarError.noTokenAvailable
        }

        // ── Stub ────────────────────────────────────────────────────────────
        // TODO(Phase 2 follow-up): POST to `<scheme>://<host>:<port>/device-token`
        //   with JSON body:
        //     { "deviceToken": tokenHex, "environment": DeviceTokenRegistrar.environment }
        //   Headers: Authorization: Bearer <credential.bearerToken>
        //   On HTTP 200 → clear the pending flag.
        //
        // For now, just mark it as pending so it is retried when the
        // endpoint becomes available.
        // ────────────────────────────────────────────────────────────────────
        UserDefaults.standard.set(true, forKey: Keys.registrationPending)
    }

    // MARK: - Environment

    /// APNs environment derived from the build configuration.
    ///
    /// Switch to `"production"` when distributing via TestFlight or the
    /// App Store (the aps-environment entitlement controls the actual
    /// environment; this string is sent to the sidecar for its records).
    nonisolated static var environment: String {
        #if DEBUG
        return "sandbox"
        #else
        return "production"
        #endif
    }

    // MARK: - Pending state helper

    /// `true` when a token has been stored but not yet acknowledged by the sidecar.
    nonisolated static var isRegistrationPending: Bool {
        UserDefaults.standard.bool(forKey: Keys.registrationPending)
    }
}

// MARK: - Errors

enum DeviceTokenRegistrarError: Error, Sendable {
    /// `didRegisterForRemoteNotificationsWithDeviceToken` has not been called yet.
    case noTokenAvailable
}
