//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import LibSignalClient
import SDWebImage
import SDWebImageWebPCoder

public class AppSetup {
    private let backgroundTask: OWSBackgroundTask
    public init() {
        self.backgroundTask = OWSBackgroundTask(label: #function)
    }
}

extension AppSetup {
    public func start(
        appContext: any AppContext,
        databaseStorage: SDSDatabaseStorage,
    ) -> AppSetup.SchemaMigrationContinuation {
        return SchemaMigrationContinuation(
            appContext: appContext,
            backgroundTask: backgroundTask,
            databaseStorage: databaseStorage,
        )
    }

    public class SchemaMigrationContinuation {
        fileprivate let appContext: any AppContext
        fileprivate let backgroundTask: OWSBackgroundTask
        fileprivate let databaseStorage: SDSDatabaseStorage

        fileprivate init(
            appContext: any AppContext,
            backgroundTask: OWSBackgroundTask,
            databaseStorage: SDSDatabaseStorage,
        ) {
            self.appContext = appContext
            self.backgroundTask = backgroundTask
            self.databaseStorage = databaseStorage
        }
    }
}

// MARK: - SchemaMigrationContinuation

extension AppSetup.SchemaMigrationContinuation {
    public func migrateDatabaseSchema() async -> AppSetup.GlobalsContinuation {
        if self.shouldTruncateGrdbWal() {
            // Try to truncate GRDB WAL before any readers or writers are active.
            do {
                databaseStorage.logFileSizes()
                try databaseStorage.grdbStorage.syncTruncatingCheckpoint()
                databaseStorage.logFileSizes()
            } catch {
                owsFailDebug("Failed to truncate database: \(error)")
            }
        }
        databaseStorage.runGrdbSchemaMigrations()
        SSKPreferences.markGRDBSchemaAsLatest()
        return AppSetup.GlobalsContinuation(
            appContext: appContext,
            backgroundTask: backgroundTask,
            databaseStorage: databaseStorage,
        )
    }

    private func shouldTruncateGrdbWal() -> Bool {
        appContext.isMainApp && appContext.mainApplicationStateOnLaunch() != .background
    }
}

// MARK: - GlobalsContinuation

extension AppSetup {
    /// Injectable mocks for global singletons accessed by tests for components
    /// that cannot be isolated in tests.
    ///
    /// For example, many legacy tests rely on the globally-available singletons
    /// from ``Dependencies`` and its ``NSObject`` extension, and use this type
    /// to inject mock versions of those singletons to the global state.
    ///
    /// Additionally, the integration tests for message backup access these
    /// globals transitively through message backup's dependency on managers
    /// that use the global state, and similarly use this type to inject a
    /// limited set of mock singletons.
    public struct TestDependencies {
        let backupAttachmentCoordinator: BackupAttachmentCoordinator?
        let contactManager: (any ContactManager)?
        let dateProvider: DateProvider?
        let groupV2Updates: (any GroupV2Updates)?
        let groupsV2: (any GroupsV2)?
        let messageSender: (AccountChecker) -> MessageSender?
        let networkManager: NetworkManager?
        let paymentsCurrencies: (any PaymentsCurrenciesSwift)?
        let paymentsHelper: (any PaymentsHelperSwift)?
        let pendingReceiptRecorder: (any PendingReceiptRecorder)?
        let profileManager: (any ProfileManager)?
        let reachabilityManager: (any SSKReachabilityManager)?
        let remoteConfigManager: (any RemoteConfigManager)?
        let signalService: (any OWSSignalServiceProtocol)?
        let storageServiceManager: (any StorageServiceManager)?
        let syncManager: (any SyncManagerProtocol)?
        let systemStoryManager: (any SystemStoryManagerProtocol)?
        let versionedProfiles: (any VersionedProfiles)?
        let webSocketFactory: (any WebSocketFactory)?

        public init(
            backupAttachmentCoordinator: BackupAttachmentCoordinator? = nil,
            contactManager: (any ContactManager)? = nil,
            dateProvider: DateProvider? = nil,
            groupV2Updates: (any GroupV2Updates)? = nil,
            groupsV2: (any GroupsV2)? = nil,
            messageSender: @escaping ((AccountChecker) -> MessageSender?) = { _ in nil },
            networkManager: NetworkManager? = nil,
            paymentsCurrencies: (any PaymentsCurrenciesSwift)? = nil,
            paymentsHelper: (any PaymentsHelperSwift)? = nil,
            pendingReceiptRecorder: (any PendingReceiptRecorder)? = nil,
            profileManager: (any ProfileManager)? = nil,
            reachabilityManager: (any SSKReachabilityManager)? = nil,
            remoteConfigManager: (any RemoteConfigManager)? = nil,
            signalService: (any OWSSignalServiceProtocol)? = nil,
            storageServiceManager: (any StorageServiceManager)? = nil,
            syncManager: (any SyncManagerProtocol)? = nil,
            systemStoryManager: (any SystemStoryManagerProtocol)? = nil,
            versionedProfiles: (any VersionedProfiles)? = nil,
            webSocketFactory: (any WebSocketFactory)? = nil,
        ) {
            self.backupAttachmentCoordinator = backupAttachmentCoordinator
            self.contactManager = contactManager
            self.dateProvider = dateProvider
            self.groupV2Updates = groupV2Updates
            self.groupsV2 = groupsV2
            self.messageSender = messageSender
            self.networkManager = networkManager
            self.paymentsCurrencies = paymentsCurrencies
            self.paymentsHelper = paymentsHelper
            self.pendingReceiptRecorder = pendingReceiptRecorder
            self.profileManager = profileManager
            self.reachabilityManager = reachabilityManager
            self.remoteConfigManager = remoteConfigManager
            self.signalService = signalService
            self.storageServiceManager = storageServiceManager
            self.syncManager = syncManager
            self.systemStoryManager = systemStoryManager
            self.versionedProfiles = versionedProfiles
            self.webSocketFactory = webSocketFactory
        }
    }

    public class GlobalsContinuation {
        fileprivate let appContext: any AppContext
        fileprivate let backgroundTask: OWSBackgroundTask
        fileprivate let databaseStorage: SDSDatabaseStorage

        fileprivate init(
            appContext: any AppContext,
            backgroundTask: OWSBackgroundTask,
            databaseStorage: SDSDatabaseStorage,
        ) {
            self.appContext = appContext
            self.backgroundTask = backgroundTask
            self.databaseStorage = databaseStorage
        }
    }
}

extension AppSetup.GlobalsContinuation {
    @MainActor
    public func initGlobals(
        appContext: AppContext,
        appReadiness: AppReadiness,
        backupArchiveErrorPresenterFactory: BackupArchiveErrorPresenterFactory,
        deviceBatteryLevelManager: (any DeviceBatteryLevelManager)?,
        deviceSleepManager: (any DeviceSleepManager)?,
        paymentsEvents: PaymentsEvents,
        mobileCoinHelper: MobileCoinHelper,
        callMessageHandler: CallMessageHandler,
        currentCallProvider: any CurrentCallProvider,
        notificationPresenter: any NotificationPresenter,
        testDependencies: AppSetup.TestDependencies = AppSetup.TestDependencies(),
    ) -> AppSetup.DataMigrationContinuation {
        configureUnsatisfiableConstraintLogging()

        // Order matters here.
        //
        // All of these "singletons" should have any dependencies used in their
        // initializers injected.
        OWSBackgroundTaskManager.shared.observeNotifications()

        let appVersion = AppVersionImpl.shared
        let webSocketFactory = testDependencies.webSocketFactory ?? WebSocketFactoryNative()

        // AFNetworking (via CFNetworking) spools its attachments in
        // NSTemporaryDirectory(). If you receive a media message while the device
        // is locked, the download will fail if the temporary directory is
        // NSFileProtectionComplete.
        let temporaryDirectory = NSTemporaryDirectory()
        owsPrecondition(OWSFileSystem.ensureDirectoryExists(temporaryDirectory))
        owsPrecondition(OWSFileSystem.protectFileOrFolder(atPath: temporaryDirectory, fileProtectionType: .completeUntilFirstUserAuthentication))

        let tsConstants = TSConstants.shared
        let dateProvider = testDependencies.dateProvider ?? Date.provider

        let tsAccountManager = TSAccountManagerImpl(
            appReadiness: appReadiness,
            dateProvider: dateProvider,
            databaseChangeObserver: databaseStorage.databaseChangeObserver,
            db: databaseStorage,
        )

        let remoteConfigProvider = RemoteConfigProviderImpl(tsAccountManager: tsAccountManager)
        let remoteConfig = databaseStorage.read { tx in
            tsAccountManager.warmCaches(tx: tx)
            return remoteConfigProvider.warmCaches(tx: tx)
        }

        let libsignalNet = Net(
            env: TSConstants.isUsingProductionService ? .production : .staging,
            userAgent: HttpHeaders.userAgentHeaderValueSignalIos,
            buildVariant: BuildFlags.netBuildVariant,
            remoteConfig: remoteConfig.netConfig(),
        )

        let cron = Cron(
            appVersion: appVersion.currentAppVersion4,
            db: databaseStorage,
        )

        let recipientDatabaseTable = RecipientDatabaseTable()
        let signalAccountStore = SignalAccountStoreImpl()
        let threadStore = ThreadStoreImpl()
        let userProfileStore = UserProfileStoreImpl()
        let usernameLookupRecordStore = UsernameLookupRecordStore()
        let nicknameRecordStore = NicknameRecordStoreImpl()
        let searchableNameIndexer = SearchableNameIndexerImpl(
            threadStore: threadStore,
            signalAccountStore: signalAccountStore,
            userProfileStore: userProfileStore,
            signalRecipientStore: recipientDatabaseTable,
            usernameLookupRecordStore: usernameLookupRecordStore,
            nicknameRecordStore: nicknameRecordStore,
        )
        let recipientFetcher = RecipientFetcher(
            recipientDatabaseTable: recipientDatabaseTable,
            searchableNameIndexer: searchableNameIndexer,
        )
        let recipientIdFinder = RecipientIdFinder(recipientDatabaseTable: recipientDatabaseTable, recipientFetcher: recipientFetcher)

        let dateProviderMonotonic = MonotonicDate.provider

        let avatarDefaultColorManager = AvatarDefaultColorManager()

        let appExpiry = AppExpiry(
            appVersion: appVersion,
        )

        let db = databaseStorage

        let networkManager = testDependencies.networkManager ?? NetworkManager(
            appReadiness: appReadiness,
            libsignalNet: libsignalNet,
        )
        let whoAmIManager = WhoAmIManagerImpl(networkManager: networkManager)

        let remoteConfigManager = testDependencies.remoteConfigManager ?? RemoteConfigManagerImpl(
            appExpiry: appExpiry,
            appReadiness: appReadiness,
            dateProvider: dateProvider,
            db: db,
            net: libsignalNet,
            networkManager: networkManager,
            remoteConfigProvider: remoteConfigProvider,
            tsAccountManager: tsAccountManager,
        )

        let preKeyStore = PreKeyStore()
        let sessionStore = SessionStore()

        let aciProtocolStore = SignalProtocolStore.build(
            dateProvider: dateProvider,
            identity: .aci,
            preKeyStore: preKeyStore,
            recipientIdFinder: recipientIdFinder,
            sessionStore: sessionStore,
        )
        let blockedRecipientStore = BlockedRecipientStore()
        let blockingManager = BlockingManager(
            blockedGroupStore: BlockedGroupStore(),
            blockedRecipientStore: blockedRecipientStore,
        )
        let earlyMessageManager = EarlyMessageManager(appReadiness: appReadiness)
        let messageProcessor = MessageProcessor(appReadiness: appReadiness)

        let groupSendEndorsementStore = GroupSendEndorsementStoreImpl()

        let messageSenderJobQueue = MessageSenderJobQueue(appReadiness: appReadiness)
        let modelReadCaches = ModelReadCaches(
            factory: ModelReadCacheFactory(appReadiness: appReadiness),
        )
        let ows2FAManager = OWS2FAManager()
        let paymentsHelper = testDependencies.paymentsHelper ?? PaymentsHelperImpl()
        let archivedPaymentStore = ArchivedPaymentStore()
        let pniProtocolStore = SignalProtocolStore.build(
            dateProvider: dateProvider,
            identity: .pni,
            preKeyStore: preKeyStore,
            recipientIdFinder: recipientIdFinder,
            sessionStore: sessionStore,
        )
        let profileManager = testDependencies.profileManager ?? OWSProfileManager(
            appReadiness: appReadiness,
            databaseStorage: databaseStorage,
        )
        let reachabilityManager = testDependencies.reachabilityManager ?? SSKReachabilityManagerImpl(
            appReadiness: appReadiness,
        )

        let receiptManager = OWSReceiptManager(appReadiness: appReadiness, databaseStorage: databaseStorage, messageSenderJobQueue: messageSenderJobQueue, notificationPresenter: notificationPresenter)
        let senderKeyStore = OldSenderKeyStore()
        let signalProtocolStoreManager = SignalProtocolStoreManager(
            aciProtocolStore: aciProtocolStore,
            pniProtocolStore: pniProtocolStore,
            preKeyStore: preKeyStore,
            sessionStore: sessionStore,
        )
        let signalService = testDependencies.signalService ?? OWSSignalService(libsignalNet: libsignalNet)
        let signalServiceAddressCache = SignalServiceAddressCache()
        let storageServiceManager = testDependencies.storageServiceManager ?? StorageServiceManagerImpl(
            appReadiness: appReadiness,
        )
        let syncManager = testDependencies.syncManager ?? OWSSyncManager(appReadiness: appReadiness)
        let udManager = OWSUDManagerImpl(
            cron: cron,
            db: databaseStorage,
            tsAccountManager: tsAccountManager,
        )
        let versionedProfiles = testDependencies.versionedProfiles ?? VersionedProfilesImpl(appReadiness: appReadiness)

        let lastVisibleInteractionStore = LastVisibleInteractionStore()
        let usernameLookupManager = UsernameLookupManagerImpl(
            searchableNameIndexer: searchableNameIndexer,
            usernameLookupRecordStore: usernameLookupRecordStore,
        )

        let nicknameManager = NicknameManagerImpl(
            nicknameRecordStore: nicknameRecordStore,
            searchableNameIndexer: searchableNameIndexer,
            storageServiceManager: storageServiceManager,
        )
        let contactManager = testDependencies.contactManager ?? OWSContactsManager(
            appReadiness: appReadiness,
            nicknameManager: nicknameManager,
            recipientDatabaseTable: recipientDatabaseTable,
            usernameLookupManager: usernameLookupManager,
        )

        let authCredentialStore = AuthCredentialStore()

        let callLinkPublicParams = try! GenericServerPublicParams(contents: tsConstants.callLinkPublicParams)
        let authCredentialManager = AuthCredentialManagerImpl(
            authCredentialStore: authCredentialStore,
            callLinkPublicParams: callLinkPublicParams,
            dateProvider: dateProvider,
            db: db,
        )

        let groupsV2 = testDependencies.groupsV2 ?? GroupsV2Impl(
            appReadiness: appReadiness,
            authCredentialStore: authCredentialStore,
            authCredentialManager: authCredentialManager,
            groupSendEndorsementStore: groupSendEndorsementStore,
        )

        let mediaBandwidthPreferenceStore = MediaBandwidthPreferenceStoreImpl(
            reachabilityManager: reachabilityManager,
        )

        let interactionStore = InteractionStoreImpl()
        let storyStore = StoryStoreImpl()

        let audioWaveformManager = AudioWaveformManagerImpl()

        let attachmentStore = AttachmentStore()

        let orphanedAttachmentCleaner = OrphanedAttachmentCleanerImpl(
            dateProvider: dateProvider,
            db: db,
        )
        let attachmentContentValidator = AttachmentContentValidatorImpl(
            attachmentStore: attachmentStore,
            audioWaveformManager: audioWaveformManager,
            dateProvider: dateProvider,
            db: db,
            orphanedAttachmentCleaner: orphanedAttachmentCleaner,
        )

        let backupNonceMetadataStore = BackupNonceMetadataStore()
        let backupExportJobStore = BackupExportJobStore()
        let backupSettingsStore = BackupSettingsStore()
        let accountKeyStore = AccountKeyStore(
            backupSettingsStore: backupSettingsStore,
        )
        let svrCredentialStorage = SVRAuthCredentialStorageImpl()
        let svrLocalStorage = SVRLocalStorageImpl()

        let accountAttributesUpdater = AccountAttributesUpdaterImpl(
            accountAttributesGenerator: AccountAttributesGenerator(
                accountKeyStore: accountKeyStore,
                ows2FAManager: ows2FAManager,
                profileManager: profileManager,
                svrLocalStorage: svrLocalStorage,
                tsAccountManager: tsAccountManager,
                udManager: udManager,
            ),
            appReadiness: appReadiness,
            cron: cron,
            dateProvider: dateProvider,
            db: db,
            networkManager: networkManager,
            profileManager: profileManager,
            svrLocalStorage: svrLocalStorage,
            syncManager: syncManager,
            tsAccountManager: tsAccountManager,
        )

        let phoneNumberDiscoverabilityManager = PhoneNumberDiscoverabilityManagerImpl(
            accountAttributesUpdater: accountAttributesUpdater,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
        )

        let subscriptionConfigManager = SubscriptionConfigManager(
            dateProvider: dateProvider,
            db: db,
            networkManager: networkManager,
        )

        let svr = SecureValueRecovery2Impl(
            appContext: SVR2.Wrappers.AppContext(),
            appReadiness: appReadiness,
            appVersion: appVersion,
            connectionFactory: SgxWebsocketConnectionFactoryImpl(websocketFactory: webSocketFactory),
            credentialStorage: svrCredentialStorage,
            db: db,
            accountKeyStore: accountKeyStore,
            scheduler: DispatchQueue(label: "org.signal.svr2", qos: .userInitiated),
            storageServiceManager: storageServiceManager,
            svrLocalStorage: svrLocalStorage,
            tsAccountManager: tsAccountManager,
            tsConstants: tsConstants,
            twoFAManager: SVR2.Wrappers.OWS2FAManager(ows2FAManager),
        )

        let backupAttachmentDownloadStore = BackupAttachmentDownloadStore()
        let backupAttachmentDownloadProgress = BackupAttachmentDownloadProgressImpl(
            appContext: appContext,
            appReadiness: appReadiness,
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            db: db,
            remoteConfigProvider: remoteConfigManager,
        )

        let backupAttachmentUploadEraStore = BackupAttachmentUploadEraStore()
        let backupAttachmentUploadProgress = BackupAttachmentUploadProgressImpl(
            attachmentStore: attachmentStore,
            backupSettingsStore: backupSettingsStore,
            db: db,
        )
        let backupAttachmentUploadStore = BackupAttachmentUploadStore()
        let backupCDNCredentialStore = BackupCDNCredentialStore()

        let backupIdService = BackupIdServiceImpl(
            accountKeyStore: accountKeyStore,
            backupSettingsStore: backupSettingsStore,
            db: db,
            networkManager: networkManager,
            tsAccountManager: tsAccountManager,
        )

        let backupPlanManager = BackupPlanManagerImpl(
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
            backupAttachmentDownloadProgress: backupAttachmentDownloadProgress,
            backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
            backupAttachmentUploadProgress: backupAttachmentUploadProgress,
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            tsAccountManager: tsAccountManager,
        )

        let backupSubscriptionIssueStore = BackupSubscriptionIssueStore()
        let backupSubscriptionManager = BackupSubscriptionManagerImpl(
            appContext: appContext,
            backupPlanManager: backupPlanManager,
            backupSubscriptionIssueStore: backupSubscriptionIssueStore,
            backupSubscriptionRedeemer: BackupSubscriptionRedeemer(
                authCredentialStore: authCredentialStore,
                backupPlanManager: backupPlanManager,
                backupSubscriptionIssueStore: backupSubscriptionIssueStore,
                dateProvider: dateProvider,
                db: db,
                reachabilityManager: reachabilityManager,
                networkManager: networkManager,
            ),
            dateProvider: dateProvider,
            db: db,
            networkManager: networkManager,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
            whoAmIManager: whoAmIManager,
        )
        let backupTestFlightEntitlementManager = BackupTestFlightEntitlementManagerImpl(
            backupPlanManager: backupPlanManager,
            backupSubscriptionIssueStore: backupSubscriptionIssueStore,
            backupSubscriptionManager: backupSubscriptionManager,
            dateProvider: dateProvider,
            db: db,
            networkManager: networkManager,
            tsAccountManager: tsAccountManager,
        )

        let inactivePrimaryDeviceStore = InactivePrimaryDeviceStore()
        let chatConnectionManager = ChatConnectionManagerImpl(
            accountManager: tsAccountManager,
            appContext: appContext,
            appExpiry: appExpiry,
            appReadiness: appReadiness,
            db: db,
            inactivePrimaryDeviceStore: inactivePrimaryDeviceStore,
            libsignalNet: libsignalNet,
        )

        let backupRequestManager = BackupRequestManagerImpl(
            backupAuthCredentialManager: BackupAuthCredentialManagerImpl(
                authCredentialStore: authCredentialStore,
                backupIdService: backupIdService,
                backupSubscriptionManager: backupSubscriptionManager,
                backupTestFlightEntitlementManager: backupTestFlightEntitlementManager,
                dateProvider: dateProvider,
                db: db,
                networkManager: networkManager,
            ),
            backupCDNCredentialStore: backupCDNCredentialStore,
            backupSettingsStore: backupSettingsStore,
            chatConnectionManager: chatConnectionManager,
            dateProvider: dateProvider,
            db: db,
            networkManager: networkManager,
        )

        let backupKeyService = BackupKeyServiceImpl(
            accountKeyStore: accountKeyStore,
            backupRequestManager: backupRequestManager,
            backupSettingsStore: backupSettingsStore,
            db: db,
            networkManager: networkManager,
        )

        let orphanedAttachmentStore = OrphanedAttachmentStore()
        let attachmentUploadStore = AttachmentUploadStore()
        let attachmentDownloadStore = AttachmentDownloadStore(dateProvider: dateProvider)

        let orphanedBackupAttachmentStore = OrphanedBackupAttachmentStore()
        let orphanedBackupAttachmentScheduler = OrphanedBackupAttachmentSchedulerImpl(
            accountKeyStore: accountKeyStore,
            orphanedBackupAttachmentStore: orphanedBackupAttachmentStore,
        )

        let attachmentThumbnailService = AttachmentThumbnailServiceImpl(remoteConfigProvider: remoteConfigProvider)

        let backupAttachmentDownloadQueueStatusManager = BackupAttachmentDownloadQueueStatusManagerImpl(
            appContext: appContext,
            appReadiness: appReadiness,
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            db: db,
            deviceBatteryLevelManager: deviceBatteryLevelManager,
            reachabilityManager: reachabilityManager,
            remoteConfigManager: remoteConfigManager,
            tsAccountManager: tsAccountManager,
        )
        let backupAttachmentUploadQueueStatusManager = BackupAttachmentUploadQueueStatusManagerImpl(
            appContext: appContext,
            appReadiness: appReadiness,
            backupAttachmentUploadStore: backupAttachmentUploadStore,
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            db: db,
            deviceBatteryLevelManager: deviceBatteryLevelManager,
            reachabilityManager: reachabilityManager,
            remoteConfigManager: remoteConfigManager,
            tsAccountManager: tsAccountManager,
        )

        let backupMediaErrorNotificationPresenter = BackupMediaErrorNotificationPresenter(
            dateProvider: dateProvider,
            db: db,
            notificationPresenter: notificationPresenter,
        )

        let backupAttachmentUploadScheduler = BackupAttachmentUploadScheduler(
            attachmentStore: attachmentStore,
            backupAttachmentUploadStore: backupAttachmentUploadStore,
            backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
            dateProvider: dateProvider,
            interactionStore: interactionStore,
            remoteConfigProvider: remoteConfigProvider,
            tsAccountManager: tsAccountManager,
        )

        let backupListMediaStore = BackupListMediaStore()
        let backupListMediaManager = BackupListMediaManagerImpl(
            accountKeyStore: accountKeyStore,
            attachmentStore: attachmentStore,
            attachmentUploadStore: attachmentUploadStore,
            backupAttachmentDownloadProgress: backupAttachmentDownloadProgress,
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
            backupAttachmentUploadProgress: backupAttachmentUploadProgress,
            backupAttachmentUploadScheduler: backupAttachmentUploadScheduler,
            backupAttachmentUploadStore: backupAttachmentUploadStore,
            backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
            backupListMediaStore: backupListMediaStore,
            backupMediaErrorNotificationPresenter: backupMediaErrorNotificationPresenter,
            backupRequestManager: backupRequestManager,
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            db: db,
            notificationPresenter: notificationPresenter,
            orphanedBackupAttachmentStore: orphanedBackupAttachmentStore,
            remoteConfigManager: remoteConfigManager,
            tsAccountManager: tsAccountManager,
        )

        let attachmentDownloadManager = AttachmentDownloadManagerImpl(
            accountKeyStore: accountKeyStore,
            appReadiness: appReadiness,
            attachmentDownloadStore: attachmentDownloadStore,
            attachmentStore: attachmentStore,
            attachmentUploadStore: attachmentUploadStore,
            attachmentValidator: attachmentContentValidator,
            backupAttachmentUploadScheduler: backupAttachmentUploadScheduler,
            backupRequestManager: backupRequestManager,
            backupSettingsStore: backupSettingsStore,
            currentCallProvider: currentCallProvider,
            dateProvider: dateProvider,
            db: db,
            interactionStore: interactionStore,
            mediaBandwidthPreferenceStore: mediaBandwidthPreferenceStore,
            orphanedAttachmentCleaner: orphanedAttachmentCleaner,
            orphanedAttachmentStore: orphanedAttachmentStore,
            orphanedBackupAttachmentScheduler: orphanedBackupAttachmentScheduler,
            profileManager: profileManager,
            reachabilityManager: reachabilityManager,
            remoteConfigProvider: remoteConfigManager,
            signalService: signalService,
            stickerManager: AttachmentDownloadManagerImpl.Wrappers.StickerManager(),
            storyStore: storyStore,
            threadStore: threadStore,
            tsAccountManager: tsAccountManager,
        )
        let backupAttachmentDownloadScheduler = BackupAttachmentDownloadSchedulerImpl(
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
        )

        let attachmentManager = AttachmentManagerImpl(
            attachmentDownloadManager: attachmentDownloadManager,
            attachmentStore: attachmentStore,
            backupAttachmentUploadScheduler: backupAttachmentUploadScheduler,
            dateProvider: dateProvider,
            orphanedAttachmentCleaner: orphanedAttachmentCleaner,
            orphanedAttachmentStore: orphanedAttachmentStore,
            orphanedBackupAttachmentScheduler: orphanedBackupAttachmentScheduler,
            remoteConfigManager: remoteConfigManager,
            stickerManager: AttachmentManagerImpl.Wrappers.StickerManager(),
        )
        let attachmentValidationBackfillMigrator = AttachmentValidationBackfillMigratorImpl(
            attachmentStore: attachmentStore,
            attachmentValidationBackfillStore: AttachmentValidationBackfillStore(),
            databaseStorage: databaseStorage,
            dateProvider: dateProvider,
            orphanedAttachmentCleaner: orphanedAttachmentCleaner,
            orphanedAttachmentStore: orphanedAttachmentStore,
            validator: attachmentContentValidator,
        )

        let quotedReplyManager = QuotedReplyManagerImpl(
            attachmentStore: attachmentStore,
            attachmentValidator: attachmentContentValidator,
            db: db,
            tsAccountManager: tsAccountManager,
        )

        let phoneNumberVisibilityFetcher = PhoneNumberVisibilityFetcherImpl(
            contactsManager: contactManager,
            tsAccountManager: tsAccountManager,
            userProfileStore: userProfileStore,
        )

        let recipientManager = SignalRecipientManagerImpl(
            phoneNumberVisibilityFetcher: phoneNumberVisibilityFetcher,
            recipientDatabaseTable: recipientDatabaseTable,
            storageServiceManager: storageServiceManager,
        )

        let badgeCountFetcher = BadgeCountFetcherImpl()

        let identityManager = OWSIdentityManagerImpl(
            appReadiness: appReadiness,
            db: db,
            messageSenderJobQueue: messageSenderJobQueue,
            networkManager: networkManager,
            notificationPresenter: notificationPresenter,
            pniProtocolStore: pniProtocolStore,
            profileManager: profileManager,
            recipientDatabaseTable: recipientDatabaseTable,
            recipientFetcher: recipientFetcher,
            recipientIdFinder: recipientIdFinder,
            sessionStore: sessionStore,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
        )

        let linkPreviewSettingStore = LinkPreviewSettingStore(keyValueStore: SSKPreferences.store)
        let linkPreviewSettingManager = LinkPreviewSettingManagerImpl(
            linkPreviewSettingStore: linkPreviewSettingStore,
            storageServiceManager: storageServiceManager,
            syncManager: syncManager,
        )

        let linkPreviewManager = LinkPreviewManagerImpl(
            attachmentStore: attachmentStore,
            attachmentValidator: attachmentContentValidator,
            db: db,
            linkPreviewSettingStore: linkPreviewSettingStore,
        )

        let editMessageStore = EditMessageStore()
        let editManager = EditManagerImpl(
            context: .init(
                attachmentContentValidator: attachmentContentValidator,
                attachmentStore: attachmentStore,
                editManagerAttachments: EditManagerAttachmentsImpl(
                    attachmentManager: attachmentManager,
                    attachmentStore: attachmentStore,
                    attachmentValidator: attachmentContentValidator,
                    linkPreviewManager: linkPreviewManager,
                ),
                editMessageStore: editMessageStore,
                receiptManagerShim: EditManagerImpl.Wrappers.ReceiptManager(receiptManager: receiptManager),
            ),
        )

        let groupUpdateItemBuilder = GroupUpdateItemBuilderImpl(
            contactsManager: contactManager,
            recipientDatabaseTable: recipientDatabaseTable,
        )

        let groupUpdateInfoMessageInserter = GroupUpdateInfoMessageInserterImpl(
            dateProvider: dateProvider,
            groupUpdateItemBuilder: groupUpdateItemBuilder,
            notificationPresenter: notificationPresenter,
        )

        let groupMemberStore = GroupMemberStoreImpl()
        let threadAssociatedDataStore = ThreadAssociatedDataStoreImpl()
        let threadReplyInfoStore = ThreadReplyInfoStore()

        let wallpaperImageStore = WallpaperImageStoreImpl(
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            attachmentValidator: attachmentContentValidator,
            db: db,
        )
        let wallpaperStore = WallpaperStore(
            wallpaperImageStore: wallpaperImageStore,
        )
        let chatColorSettingStore = ChatColorSettingStore(
            wallpaperStore: wallpaperStore,
        )

        let groupMemberUpdater = GroupMemberUpdaterImpl(
            temporaryShims: GroupMemberUpdaterTemporaryShimsImpl(),
            groupMemberStore: groupMemberStore,
            signalServiceAddressCache: signalServiceAddressCache,
        )

        let messageSendLog = MessageSendLog(
            db: db,
            dateProvider: { Date() },
        )

        let callLinkStore = CallLinkRecordStoreImpl()
        let deletedCallRecordStore = DeletedCallRecordStoreImpl()
        let deletedCallRecordExpirationJob = DeletedCallRecordExpirationJob(
            callLinkStore: callLinkStore,
            dateProvider: dateProvider,
            db: db,
            deletedCallRecordStore: deletedCallRecordStore,
        )
        let callRecordStore = CallRecordStoreImpl(
            deletedCallRecordStore: deletedCallRecordStore,
        )
        let callRecordSyncMessageConversationIdAdapater = CallRecordSyncMessageConversationIdAdapterImpl(
            callLinkStore: callLinkStore,
            callRecordStore: callRecordStore,
            recipientDatabaseTable: recipientDatabaseTable,
            threadStore: threadStore,
        )
        let outgoingCallEventSyncMessageManager = OutgoingCallEventSyncMessageManagerImpl(
            appReadiness: appReadiness,
            databaseStorage: databaseStorage,
            messageSenderJobQueue: messageSenderJobQueue,
            callRecordConversationIdAdapter: callRecordSyncMessageConversationIdAdapater,
        )
        let adHocCallRecordManager = AdHocCallRecordManagerImpl(
            callRecordStore: callRecordStore,
            callLinkStore: callLinkStore,
            outgoingSyncMessageManager: outgoingCallEventSyncMessageManager,
        )
        let groupCallRecordManager = GroupCallRecordManagerImpl(
            callRecordStore: callRecordStore,
            interactionStore: interactionStore,
            outgoingSyncMessageManager: outgoingCallEventSyncMessageManager,
        )
        let individualCallRecordManager = IndividualCallRecordManagerImpl(
            callRecordStore: callRecordStore,
            interactionStore: interactionStore,
            outgoingSyncMessageManager: outgoingCallEventSyncMessageManager,
        )
        let callRecordQuerier = CallRecordQuerierImpl()
        let callRecordMissedCallManager = CallRecordMissedCallManagerImpl(
            callRecordConversationIdAdapter: callRecordSyncMessageConversationIdAdapater,
            callRecordQuerier: callRecordQuerier,
            callRecordStore: callRecordStore,
            syncMessageSender: CallRecordMissedCallManagerImpl.Wrappers.SyncMessageSender(messageSenderJobQueue),
        )
        let callRecordDeleteManager = CallRecordDeleteManagerImpl(
            callRecordStore: callRecordStore,
            outgoingCallEventSyncMessageManager: outgoingCallEventSyncMessageManager,
            deletedCallRecordExpirationJob: deletedCallRecordExpirationJob,
            deletedCallRecordStore: deletedCallRecordStore,
            threadStore: threadStore,
        )

        let deleteForMeOutgoingSyncMessageManager = DeleteForMeOutgoingSyncMessageManagerImpl(
            recipientDatabaseTable: recipientDatabaseTable,
            syncMessageSender: DeleteForMeOutgoingSyncMessageManagerImpl.Wrappers.SyncMessageSender(messageSenderJobQueue),
            threadStore: threadStore,
        )
        let interactionDeleteManager = InteractionDeleteManagerImpl(
            callRecordStore: callRecordStore,
            callRecordDeleteManager: callRecordDeleteManager,
            databaseStorage: databaseStorage,
            deleteForMeOutgoingSyncMessageManager: deleteForMeOutgoingSyncMessageManager,
            interactionReadCache: modelReadCaches.interactionReadCache,
            interactionStore: interactionStore,
            messageSendLog: messageSendLog,
            tsAccountManager: tsAccountManager,
        )

        let disappearingMessagesConfigurationStore = DisappearingMessagesConfigurationStoreImpl()
        let disappearingMessagesExpirationJob = DisappearingMessagesExpirationJob(
            dateProvider: dateProvider,
            db: db,
            interactionDeleteManager: interactionDeleteManager,
        )

        let callRecordDeleteAllJobQueue = CallRecordDeleteAllJobQueue(
            callLinkStore: callLinkStore,
            callRecordConversationIdAdapter: callRecordSyncMessageConversationIdAdapater,
            callRecordDeleteManager: callRecordDeleteManager,
            callRecordQuerier: callRecordQuerier,
            db: db,
            interactionDeleteManager: interactionDeleteManager,
            messageSenderJobQueue: messageSenderJobQueue,
        )
        let incomingCallEventSyncMessageManager = IncomingCallEventSyncMessageManagerImpl(
            adHocCallRecordManager: adHocCallRecordManager,
            callLinkStore: callLinkStore,
            callRecordStore: callRecordStore,
            callRecordDeleteManager: callRecordDeleteManager,
            groupCallRecordManager: groupCallRecordManager,
            individualCallRecordManager: individualCallRecordManager,
            interactionDeleteManager: interactionDeleteManager,
            interactionStore: interactionStore,
            markAsReadShims: IncomingCallEventSyncMessageManagerImpl.ShimsImpl.MarkAsRead(
                notificationPresenter: notificationPresenter,
            ),
            recipientDatabaseTable: recipientDatabaseTable,
            threadStore: threadStore,
        )
        let incomingCallLogEventSyncMessageManager = IncomingCallLogEventSyncMessageManagerImpl(
            callRecordConversationIdAdapter: callRecordSyncMessageConversationIdAdapater,
            deleteAllCallsJobQueue: IncomingCallLogEventSyncMessageManagerImpl.Wrappers.DeleteAllCallsJobQueue(
                callRecordDeleteAllJobQueue,
            ),
            missedCallManager: callRecordMissedCallManager,
        )

        let threadSoftDeleteManager = ThreadSoftDeleteManagerImpl(
            deleteForMeOutgoingSyncMessageManager: deleteForMeOutgoingSyncMessageManager,
            intentsManager: ThreadSoftDeleteManagerImpl.Wrappers.IntentsManager(),
            interactionDeleteManager: interactionDeleteManager,
            recipientDatabaseTable: recipientDatabaseTable,
            storyManager: ThreadSoftDeleteManagerImpl.Wrappers.StoryManager(),
            threadReplyInfoStore: threadReplyInfoStore,
            tsAccountManager: tsAccountManager,
        )

        let deleteForMeAddressableMessageFinder = DeleteForMeAddressableMessageFinderImpl()
        let bulkDeleteInteractionJobQueue = BulkDeleteInteractionJobQueue(
            addressableMessageFinder: deleteForMeAddressableMessageFinder,
            db: db,
            interactionDeleteManager: interactionDeleteManager,
            threadSoftDeleteManager: threadSoftDeleteManager,
            threadStore: threadStore,
        )
        let deleteForMeIncomingSyncMessageManager = DeleteForMeIncomingSyncMessageManagerImpl(
            addressableMessageFinder: deleteForMeAddressableMessageFinder,
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            bulkDeleteInteractionJobQueue: bulkDeleteInteractionJobQueue,
            interactionDeleteManager: interactionDeleteManager,
            recipientDatabaseTable: recipientDatabaseTable,
            threadSoftDeleteManager: threadSoftDeleteManager,
            threadStore: threadStore,
            tsAccountManager: tsAccountManager,
        )

        let threadRemover = ThreadRemoverImpl(
            chatColorSettingStore: chatColorSettingStore,
            databaseStorage: ThreadRemoverImpl.Wrappers.DatabaseStorage(databaseStorage),
            disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
            lastVisibleInteractionStore: lastVisibleInteractionStore,
            threadAssociatedDataStore: threadAssociatedDataStore,
            threadReadCache: ThreadRemoverImpl.Wrappers.ThreadReadCache(modelReadCaches.threadReadCache),
            threadReplyInfoStore: threadReplyInfoStore,
            threadSoftDeleteManager: threadSoftDeleteManager,
            threadStore: threadStore,
            wallpaperStore: wallpaperStore,
        )

        let pinnedThreadStore = PinnedThreadStoreImpl()
        let pinnedThreadManager = PinnedThreadManagerImpl(
            db: db,
            pinnedThreadStore: pinnedThreadStore,
            storageServiceManager: storageServiceManager,
            threadStore: threadStore,
        )

        let pinnedMessageExpirationJob = PinnedMessageExpirationJob(
            dateProvider: dateProvider,
            db: db,
        )

        let pinnedMessageManager = PinnedMessageManager(
            disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
            interactionStore: interactionStore,
            accountManager: tsAccountManager,
            db: db,
            threadStore: threadStore,
            dateProvider: dateProvider,
            expirationJob: pinnedMessageExpirationJob,
        )

        let storyRecipientStore = StoryRecipientStore()
        let storyRecipientManager = StoryRecipientManager(
            recipientDatabaseTable: recipientDatabaseTable,
            storyRecipientStore: storyRecipientStore,
            storageServiceManager: storageServiceManager,
            threadStore: threadStore,
        )
        let storyMessageExpirationJob = StoryMessageExpirationJob(
            dateProvider: dateProvider,
            db: db,
        )

        let authorMergeHelper = AuthorMergeHelper()
        let recipientMerger = RecipientMergerImpl(
            blockedRecipientStore: blockedRecipientStore,
            identityManager: identityManager,
            observers: RecipientMergerImpl.buildObservers(
                authorMergeHelper: authorMergeHelper,
                callRecordStore: callRecordStore,
                chatColorSettingStore: chatColorSettingStore,
                deletedCallRecordStore: deletedCallRecordStore,
                disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
                groupMemberUpdater: groupMemberUpdater,
                groupMemberStore: groupMemberStore,
                interactionStore: interactionStore,
                pinnedThreadManager: pinnedThreadManager,
                profileManager: profileManager,
                recipientMergeNotifier: RecipientMergeNotifier(),
                signalServiceAddressCache: signalServiceAddressCache,
                threadAssociatedDataStore: threadAssociatedDataStore,
                threadRemover: threadRemover,
                threadReplyInfoStore: threadReplyInfoStore,
                threadStore: threadStore,
                userProfileStore: userProfileStore,
                wallpaperImageStore: wallpaperImageStore,
                wallpaperStore: wallpaperStore,
            ),
            recipientDatabaseTable: recipientDatabaseTable,
            recipientFetcher: recipientFetcher,
            searchableNameIndexer: searchableNameIndexer,
            sessionStore: sessionStore,
            storageServiceManager: storageServiceManager,
            storyRecipientStore: storyRecipientStore,
        )

        let accountEntropyPoolManager = AccountEntropyPoolManagerImpl(
            accountAttributesUpdater: accountAttributesUpdater,
            accountKeyStore: accountKeyStore,
            appContext: appContext,
            backupSettingsStore: backupSettingsStore,
            db: db,
            storageServiceManager: storageServiceManager,
            svr: svr,
            syncManager: syncManager,
            tsAccountManager: tsAccountManager,
        )

        let keyTransparencyStore = KeyTransparencyStore()

        let registrationStateChangeManager = RegistrationStateChangeManagerImpl(
            authCredentialStore: authCredentialStore,
            backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
            backupCDNCredentialStore: backupCDNCredentialStore,
            backupSubscriptionManager: backupSubscriptionManager,
            backupTestFlightEntitlementManager: backupTestFlightEntitlementManager,
            blockedRecipientStore: blockedRecipientStore,
            chatConnectionManager: chatConnectionManager,
            cron: cron,
            db: db,
            dmConfigurationStore: disappearingMessagesConfigurationStore,
            identityManager: identityManager,
            keyTransparencyStore: keyTransparencyStore,
            networkManager: networkManager,
            notificationPresenter: notificationPresenter,
            paymentsEvents: paymentsEvents,
            recipientManager: recipientManager,
            recipientMerger: recipientMerger,
            senderKeyStore: senderKeyStore,
            signalProtocolStoreManager: signalProtocolStoreManager,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
            udManager: udManager,
            versionedProfiles: versionedProfiles,
        )
        chatConnectionManager.onRegistrationStateChange = { [weak registrationStateChangeManager] isDelinkedOrDeregistered, tx in
            registrationStateChangeManager?.setIsDeregisteredOrDelinked(isDelinkedOrDeregistered, tx: tx)
        }

        let attachmentUploadManager = AttachmentUploadManagerImpl(
            accountKeyStore: accountKeyStore,
            attachmentEncrypter: Upload.Wrappers.AttachmentEncrypter(),
            attachmentStore: attachmentStore,
            attachmentUploadStore: attachmentUploadStore,
            attachmentThumbnailService: attachmentThumbnailService,
            backupRequestManager: backupRequestManager,
            chatConnectionManager: chatConnectionManager,
            dateProvider: dateProvider,
            db: db,
            fileSystem: Upload.Wrappers.FileSystem(),
            interactionStore: interactionStore,
            remoteConfigProvider: remoteConfigManager,
            signalService: signalService,
            sleepTimer: Upload.Wrappers.SleepTimer(),
            storyStore: storyStore,
        )
        let backupAttachmentCoordinator = testDependencies.backupAttachmentCoordinator ?? BackupAttachmentCoordinatorImpl(
            appContext: appContext,
            appReadiness: appReadiness,
            backupSettingsStore: backupSettingsStore,
            db: db,
            downloadRunner: BackupAttachmentDownloadQueueRunnerImpl(
                appContext: appContext,
                attachmentStore: attachmentStore,
                attachmentDownloadManager: attachmentDownloadManager,
                attachmentUploadStore: attachmentUploadStore,
                backupAttachmentDownloadStore: backupAttachmentDownloadStore,
                backupAttachmentUploadScheduler: backupAttachmentUploadScheduler,
                backupMediaErrorNotificationPresenter: backupMediaErrorNotificationPresenter,
                backupListMediaManager: backupListMediaManager,
                backupSettingsStore: backupSettingsStore,
                dateProvider: dateProvider,
                db: db,
                mediaBandwidthPreferenceStore: mediaBandwidthPreferenceStore,
                progress: backupAttachmentDownloadProgress,
                remoteConfigProvider: remoteConfigManager,
                statusManager: backupAttachmentDownloadQueueStatusManager,
                tsAccountManager: tsAccountManager,
            ),
            listMediaManager: backupListMediaManager,
            offloadingManager: AttachmentOffloadingManagerImpl(
                attachmentStore: attachmentStore,
                attachmentThumbnailService: attachmentThumbnailService,
                backupAttachmentDownloadStore: backupAttachmentDownloadStore,
                backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
                backupSettingsStore: backupSettingsStore,
                dateProvider: dateProvider,
                db: db,
                listMediaManager: backupListMediaManager,
                orphanedAttachmentCleaner: orphanedAttachmentCleaner,
                orphanedAttachmentStore: orphanedAttachmentStore,
                tsAccountManager: tsAccountManager,
            ),
            orphanRunner: OrphanedBackupAttachmentQueueRunnerImpl(
                accountKeyStore: accountKeyStore,
                appReadiness: appReadiness,
                attachmentStore: attachmentStore,
                backupRequestManager: backupRequestManager,
                backupSettingsStore: backupSettingsStore,
                dateProvider: dateProvider,
                db: db,
                listMediaManager: backupListMediaManager,
                orphanedBackupAttachmentStore: orphanedBackupAttachmentStore,
                tsAccountManager: tsAccountManager,
            ),
            orphanStore: orphanedBackupAttachmentStore,
            tsAccountManager: tsAccountManager,
            uploadRunner: BackupAttachmentUploadQueueRunnerImpl(
                accountKeyStore: accountKeyStore,
                attachmentStore: attachmentStore,
                attachmentUploadManager: attachmentUploadManager,
                backupAttachmentUploadScheduler: backupAttachmentUploadScheduler,
                backupAttachmentUploadStore: backupAttachmentUploadStore,
                backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
                backupListMediaManager: backupListMediaManager,
                backupMediaErrorNotificationPresenter: backupMediaErrorNotificationPresenter,
                backupRequestManager: backupRequestManager,
                backupSettingsStore: backupSettingsStore,
                dateProvider: dateProvider,
                db: db,
                notificationPresenter: notificationPresenter,
                orphanedBackupAttachmentStore: orphanedBackupAttachmentStore,
                progress: backupAttachmentUploadProgress,
                statusManager: backupAttachmentUploadQueueStatusManager,
                tsAccountManager: tsAccountManager,
            ),
        )

        let attachmentBackfillManager = AttachmentBackfillManager(
            attachmentStore: attachmentStore,
            attachmentUploadManager: attachmentUploadManager,
            db: db,
            interactionStore: interactionStore,
            notificationPresenter: notificationPresenter,
            recipientDatabaseTable: recipientDatabaseTable,
            syncMessageSender: messageSenderJobQueue,
            threadStore: threadStore,
        )

        let accountChecker = AccountChecker(
            db: db,
            networkManager: networkManager,
            recipientFetcher: recipientFetcher,
            recipientManager: recipientManager,
            recipientMerger: recipientMerger,
            recipientStore: recipientDatabaseTable,
            tsAccountManager: tsAccountManager,
            chatConnectionManager: chatConnectionManager,
        )

        let messageSender = testDependencies.messageSender(accountChecker) ?? MessageSender(
            accountChecker: accountChecker,
            groupSendEndorsementStore: groupSendEndorsementStore,
        )

        let pniDistributionParameterBuilder = PniDistributionParameterBuilderImpl(
            db: db,
            messageSender: PniDistributionParameterBuilderImpl.Wrappers.MessageSender(messageSender),
            pniKyberPreKeyStore: pniProtocolStore.kyberPreKeyStore,
            registrationIdGenerator: RegistrationIdGenerator(),
        )

        let identityKeyChecker = IdentityKeyCheckerImpl(
            db: db,
            identityManager: identityManager,
            profileFetcher: IdentityKeyCheckerImpl.Wrappers.ProfileFetcher(networkManager: networkManager),
        )
        let identityKeyMismatchManager = IdentityKeyMismatchManagerImpl(
            db: db,
            identityKeyChecker: identityKeyChecker,
            messageProcessor: IdentityKeyMismatchManagerImpl.Wrappers.MessageProcessor(messageProcessor),
            registrationStateChangeManager: registrationStateChangeManager,
            tsAccountManager: tsAccountManager,
            whoAmIManager: whoAmIManager,
        )

        let preKeyTaskAPIClient = PreKeyTaskAPIClientImpl(networkManager: networkManager)
        let preKeyManager = PreKeyManagerImpl(
            dateProvider: dateProvider,
            db: db,
            identityKeyMismatchManager: identityKeyMismatchManager,
            identityManager: identityManager,
            messageProcessor: messageProcessor,
            preKeyTaskAPIClient: preKeyTaskAPIClient,
            protocolStoreManager: signalProtocolStoreManager,
            remoteConfigProvider: remoteConfigManager,
            chatConnectionManager: chatConnectionManager,
            tsAccountManager: tsAccountManager,
        )

        let changePhoneNumberPniManager = ChangePhoneNumberPniManagerImpl(
            db: db,
            identityManager: identityManager,
            pniDistributionParameterBuilder: pniDistributionParameterBuilder,
            pniSignedPreKeyStore: pniProtocolStore.signedPreKeyStore,
            pniKyberPreKeyStore: pniProtocolStore.kyberPreKeyStore,
            preKeyManager: preKeyManager,
            registrationIdGenerator: RegistrationIdGenerator(),
            tsAccountManager: tsAccountManager,
        )

        let registrationSessionManager = RegistrationSessionManagerImpl(
            dateProvider: dateProvider,
            db: db,
            signalService: signalService,
        )

        let recipientHidingManager = RecipientHidingManagerImpl(
            profileManager: profileManager,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
            messageSenderJobQueue: messageSenderJobQueue,
        )

        let donationReceiptCredentialResultStore = DonationReceiptCredentialResultStore()

        let usernameApiClient = UsernameApiClientImpl(
            networkManager: networkManager,
            chatConnectionManager: chatConnectionManager,
        )
        let usernameEducationManager = UsernameEducationManagerImpl()
        let usernameLinkManager = UsernameLinkManagerImpl(
            db: db,
            apiClient: usernameApiClient,
        )
        let localUsernameManager = LocalUsernameManagerImpl(
            db: db,
            reachabilityManager: reachabilityManager,
            storageServiceManager: storageServiceManager,
            usernameApiClient: usernameApiClient,
            usernameLinkManager: usernameLinkManager,
        )
        let usernameValidationManager = UsernameValidationManagerImpl(context: .init(
            database: db,
            localUsernameManager: localUsernameManager,
            messageProcessor: Usernames.Validation.Wrappers.MessageProcessor(messageProcessor),
            storageServiceManager: Usernames.Validation.Wrappers.StorageServiceManager(storageServiceManager),
            usernameLinkManager: usernameLinkManager,
            whoAmIManager: whoAmIManager,
        ))

        let keyTransparencyManager = KeyTransparencyManager(
            chatConnectionManager: chatConnectionManager,
            dateProvider: dateProvider,
            db: db,
            identityManager: identityManager,
            keyTransparencyStore: keyTransparencyStore,
            localUsernameManager: localUsernameManager,
            recipientDatabaseTable: recipientDatabaseTable,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
            udManager: udManager,
        )

        let incomingPniChangeNumberProcessor = IncomingPniChangeNumberProcessorImpl(
            identityManager: identityManager,
            pniProtocolStore: pniProtocolStore,
            preKeyManager: preKeyManager,
            registrationStateChangeManager: registrationStateChangeManager,
            tsAccountManager: tsAccountManager,
        )

        let masterKeySyncManager = MasterKeySyncManagerImpl(
            dateProvider: dateProvider,
            svr: svr,
            syncManager: syncManager,
            tsAccountManager: tsAccountManager,
        )

        let messageStickerManager = MessageStickerManagerImpl(
            attachmentStore: attachmentStore,
            attachmentValidator: attachmentContentValidator,
        )

        let contactShareManager = ContactShareManagerImpl(
            attachmentStore: attachmentStore,
            attachmentValidator: attachmentContentValidator,
        )

        let pollMessageManager = PollMessageManager(
            pollStore: PollStore(),
            recipientDatabaseTable: recipientDatabaseTable,
            interactionStore: interactionStore,
            accountManager: tsAccountManager,
            messageSenderJobQueue: messageSenderJobQueue,
            disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
            attachmentContentValidator: attachmentContentValidator,
            db: db,
        )

        let sentMessageTranscriptReceiver = SentMessageTranscriptReceiverImpl(
            attachmentDownloads: attachmentDownloadManager,
            attachmentManager: attachmentManager,
            disappearingMessagesExpirationJob: disappearingMessagesExpirationJob,
            earlyMessageManager: SentMessageTranscriptReceiverImpl.Wrappers.EarlyMessageManager(earlyMessageManager),
            groupManager: SentMessageTranscriptReceiverImpl.Wrappers.GroupManager(),
            interactionDeleteManager: interactionDeleteManager,
            interactionStore: interactionStore,
            messageStickerManager: messageStickerManager,
            paymentsHelper: paymentsHelper,
            pollMessageManager: pollMessageManager,
            signalProtocolStoreManager: signalProtocolStoreManager,
            tsAccountManager: tsAccountManager,
            viewOnceMessages: SentMessageTranscriptReceiverImpl.Wrappers.ViewOnceMessages(),
        )

        let preferences = Preferences()
        let systemStoryManager = testDependencies.systemStoryManager ?? SystemStoryManager(
            appReadiness: appReadiness,
            messageProcessor: messageProcessor,
        )
        let typingIndicators = TypingIndicatorsImpl()

        let privateStoryThreadDeletionManager = PrivateStoryThreadDeletionManagerImpl(
            dateProvider: dateProvider,
            remoteConfigProvider: remoteConfigManager,
            storageServiceManager: storageServiceManager,
            threadRemover: threadRemover,
            threadStore: threadStore,
        )

        let reactionStore: any ReactionStore = ReactionStoreImpl()

        let storageServiceRecordIkmMigrator = StorageServiceRecordIkmMigratorImpl(
            db: db,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
        )

        let profileFetcher = ProfileFetcherImpl(
            accountChecker: accountChecker,
            db: db,
            disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
            identityManager: identityManager,
            paymentsHelper: paymentsHelper,
            profileManager: profileManager,
            reachabilityManager: reachabilityManager,
            recipientDatabaseTable: recipientDatabaseTable,
            syncManager: syncManager,
            tsAccountManager: tsAccountManager,
            udManager: udManager,
            versionedProfiles: versionedProfiles,
        )

        let messagePipelineSupervisor = MessagePipelineSupervisor()

        let adminDeleteManager = AdminDeleteManager(
            recipientDatabaseTable: recipientDatabaseTable,
            tsAccountManager: tsAccountManager,
            storageServiceManager: storageServiceManager,
        )

        let backupChatStyleArchiver = BackupArchiveChatStyleArchiver(
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            backupAttachmentDownloadScheduler: backupAttachmentDownloadScheduler,
            chatColorSettingStore: chatColorSettingStore,
            wallpaperStore: wallpaperStore,
        )

        let backupInteractionStore = BackupArchiveInteractionStore(interactionStore: interactionStore)
        let backupRecipientStore = BackupArchiveRecipientStore(
            recipientTable: recipientDatabaseTable,
            searchableNameIndexer: searchableNameIndexer,
        )
        let backupStickerPackDownloadStore = BackupStickerPackDownloadStoreImpl()
        let backupStoryStore = BackupArchiveStoryStore(
            storyStore: storyStore,
            storyRecipientStore: storyRecipientStore,
        )
        let backupThreadStore = BackupArchiveThreadStore(threadStore: threadStore)

        let backupArchiveErrorPresenter = backupArchiveErrorPresenterFactory.build(
            db: db,
            tsAccountManager: tsAccountManager,
        )
        let backupArchiveAvatarFetcher = BackupArchiveAvatarFetcher(
            appReadiness: appReadiness,
            dateProvider: dateProvider,
            db: db,
            groupsV2: groupsV2,
            profileFetcher: profileFetcher,
            profileManager: profileManager,
            reachabilityManager: reachabilityManager,
            threadStore: threadStore,
            tsAccountManager: tsAccountManager,
        )
        let backupContactRecipientArchiver = BackupArchiveContactRecipientArchiver(
            avatarDefaultColorManager: avatarDefaultColorManager,
            avatarFetcher: backupArchiveAvatarFetcher,
            blockingManager: BackupArchive.Wrappers.BlockingManager(blockingManager),
            contactManager: BackupArchive.Wrappers.ContactManager(contactManager),
            keyTransparencyStore: keyTransparencyStore,
            nicknameManager: nicknameManager,
            profileManager: BackupArchive.Wrappers.ProfileManager(profileManager),
            recipientHidingManager: recipientHidingManager,
            recipientManager: recipientManager,
            recipientStore: backupRecipientStore,
            signalServiceAddressCache: signalServiceAddressCache,
            storyStore: backupStoryStore,
            threadStore: backupThreadStore,
            tsAccountManager: tsAccountManager,
            usernameLookupManager: usernameLookupManager,
        )

        let backupAttachmentsArchiver = BackupArchiveMessageAttachmentArchiver(
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            backupAttachmentDownloadScheduler: backupAttachmentDownloadScheduler,
        )
        let backupsOversizeTextArchiver = BackupArchiveInlinedOversizeTextArchiver(
            attachmentsArchiver: backupAttachmentsArchiver,
            attachmentContentValidator: attachmentContentValidator,
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            db: db,
            orphanedAttachmentStore: orphanedAttachmentStore,
        )
        let backupReactionArchiver = BackupArchiveReactionArchiver(
            reactionStore: BackupArchiveReactionStore(),
        )
        let pollArchiver = BackupArchivePollArchiver(
            pollManager: pollMessageManager,
            db: db,
            recipientDatabaseTable: recipientDatabaseTable,
            reactionArchiver: backupReactionArchiver,
        )
        let backupArchiveManager = BackupArchiveManagerImpl(
            accountDataArchiver: BackupArchiveAccountDataArchiver(
                backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
                backupSettingsStore: backupSettingsStore,
                backupSubscriptionManager: backupSubscriptionManager,
                callServiceSettingsStore: CallServiceSettingsStore(),
                chatStyleArchiver: backupChatStyleArchiver,
                disappearingMessageConfigurationStore: disappearingMessagesConfigurationStore,
                donationSubscriptionManager: BackupArchive.Wrappers.DonationSubscriptionManager(),
                imageQuality: BackupArchive.Wrappers.ImageQuality(),
                keyTransparencyManager: keyTransparencyManager,
                keyTransparencyStore: keyTransparencyStore,
                linkPreviewSettingStore: linkPreviewSettingStore,
                localUsernameManager: localUsernameManager,
                mediaBandwidthPreferenceStore: mediaBandwidthPreferenceStore,
                ows2FAManager: BackupArchive.Wrappers.OWS2FAManager(ows2FAManager),
                phoneNumberDiscoverabilityManager: phoneNumberDiscoverabilityManager,
                preferences: BackupArchive.Wrappers.Preferences(preferences: preferences),
                profileManager: BackupArchive.Wrappers.ProfileManager(profileManager),
                receiptManager: BackupArchive.Wrappers.ReceiptManager(receiptManager: receiptManager),
                reactionManager: BackupArchive.Wrappers.ReactionManager(),
                screenLock: BackupArchive.Wrappers.ScreenLock(),
                sskPreferences: BackupArchive.Wrappers.SSKPreferences(),
                storyManager: BackupArchive.Wrappers.StoryManager(),
                systemStoryManager: BackupArchive.Wrappers.SystemStoryManager(systemStoryManager: systemStoryManager),
                theme: ThemeDataStore(),
                typingIndicators: BackupArchive.Wrappers.TypingIndicators(typingIndicators: typingIndicators),
                udManager: BackupArchive.Wrappers.UDManager(udManager: udManager),
                usernameEducationManager: usernameEducationManager,
            ),
            adHocCallArchiver: BackupArchiveAdHocCallArchiver(
                callRecordStore: callRecordStore,
                callLinkRecordStore: callLinkStore,
                adHocCallRecordManager: adHocCallRecordManager,
            ),
            appVersion: appVersion,
            attachmentDownloadManager: attachmentDownloadManager,
            attachmentUploadManager: attachmentUploadManager,
            avatarFetcher: backupArchiveAvatarFetcher,
            backupArchiveErrorPresenter: backupArchiveErrorPresenter,
            backupAttachmentCoordinator: backupAttachmentCoordinator,
            backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
            backupNonceMetadataStore: backupNonceMetadataStore,
            backupRequestManager: backupRequestManager,
            backupSettingsStore: backupSettingsStore,
            backupStickerPackDownloadStore: backupStickerPackDownloadStore,
            callLinkRecipientArchiver: BackupArchiveCallLinkRecipientArchiver(
                callLinkStore: callLinkStore,
            ),
            chatArchiver: BackupArchiveChatArchiver(
                chatStyleArchiver: backupChatStyleArchiver,
                contactRecipientArchiver: backupContactRecipientArchiver,
                dmConfigurationStore: disappearingMessagesConfigurationStore,
                pinnedThreadStore: pinnedThreadStore,
                threadStore: backupThreadStore,
            ),
            chatItemArchiver: BackupArchiveChatItemArchiver(
                archivedPaymentStore: archivedPaymentStore,
                attachmentsArchiver: backupAttachmentsArchiver,
                attachmentStore: attachmentStore,
                callRecordStore: callRecordStore,
                contactManager: BackupArchive.Wrappers.ContactManager(contactManager),
                editMessageStore: editMessageStore,
                groupCallRecordManager: groupCallRecordManager,
                groupUpdateItemBuilder: groupUpdateItemBuilder,
                individualCallRecordManager: individualCallRecordManager,
                interactionStore: backupInteractionStore,
                oversizeTextArchiver: backupsOversizeTextArchiver,
                pollArchiver: pollArchiver,
                reactionStore: reactionStore,
                threadStore: backupThreadStore,
                reactionArchiver: backupReactionArchiver,
                pinnedMessageManager: pinnedMessageManager,
                adminDeleteManager: adminDeleteManager,
            ),
            contactRecipientArchiver: backupContactRecipientArchiver,
            databaseChangeObserver: databaseStorage.databaseChangeObserver,
            dateProvider: dateProvider,
            dateProviderMonotonic: dateProviderMonotonic,
            db: db,
            disappearingMessagesExpirationJob: disappearingMessagesExpirationJob,
            distributionListRecipientArchiver: BackupArchiveDistributionListRecipientArchiver(
                privateStoryThreadDeletionManager: privateStoryThreadDeletionManager,
                storyStore: backupStoryStore,
                threadStore: backupThreadStore,
            ),
            encryptedStreamProvider: BackupArchiveEncryptedProtoStreamProvider(),
            fullTextSearchIndexer: BackupArchiveFullTextSearchIndexerImpl(
                appReadiness: appReadiness,
                dateProvider: dateProviderMonotonic,
                db: db,
                interactionStore: interactionStore,
                searchableNameIndexer: searchableNameIndexer,
            ),
            groupRecipientArchiver: BackupArchiveGroupRecipientArchiver(
                avatarDefaultColorManager: avatarDefaultColorManager,
                avatarFetcher: backupArchiveAvatarFetcher,
                blockingManager: BackupArchive.Wrappers.BlockingManager(blockingManager),
                disappearingMessageConfigStore: disappearingMessagesConfigurationStore,
                groupsV2: groupsV2,
                profileManager: BackupArchive.Wrappers.ProfileManager(profileManager),
                storyStore: backupStoryStore,
                threadStore: backupThreadStore,
            ),
            libsignalNet: libsignalNet,
            localStorage: accountKeyStore,
            localRecipientArchiver: BackupArchiveLocalRecipientArchiver(
                avatarDefaultColorManager: avatarDefaultColorManager,
                profileManager: BackupArchive.Wrappers.ProfileManager(profileManager),
                recipientStore: backupRecipientStore,
            ),
            messagePipelineSupervisor: messagePipelineSupervisor,
            oversizeTextArchiver: backupsOversizeTextArchiver,
            plaintextStreamProvider: BackupArchivePlaintextProtoStreamProvider(),
            postFrameRestoreActionManager: BackupArchivePostFrameRestoreActionManager(
                avatarFetcher: backupArchiveAvatarFetcher,
                dateProvider: dateProvider,
                interactionStore: backupInteractionStore,
                lastVisibleInteractionStore: lastVisibleInteractionStore,
                preferences: BackupArchive.Wrappers.Preferences(preferences: preferences),
                recipientDatabaseTable: recipientDatabaseTable,
                sskPreferences: BackupArchive.Wrappers.SSKPreferences(),
                threadStore: backupThreadStore,
            ),
            releaseNotesRecipientArchiver: BackupArchiveReleaseNotesRecipientArchiver(),
            remoteConfigManager: remoteConfigManager,
            stickerPackArchiver: BackupArchiveStickerPackArchiver(
                backupStickerPackDownloadStore: backupStickerPackDownloadStore,
            ),
            tsAccountManager: tsAccountManager,
        )

        let externalPendingIDEALDonationStore = ExternalPendingIDEALDonationStoreImpl()

        // TODO: Move this into ProfileFetcherJob.
        // Ideally, this would be a private implementation detail of that class.
        // However, that class is currently implemented mostly as static methods,
        // so there's no place to store it. Once it's protocolized, this type
        // should be initialized in its initializer.
        let localProfileChecker = LocalProfileChecker(
            db: db,
            messageProcessor: messageProcessor,
            profileManager: profileManager,
            storageServiceManager: storageServiceManager,
            tsAccountManager: tsAccountManager,
            udManager: udManager,
        )

        let attachmentViewOnceManager = AttachmentViewOnceManagerImpl(
            attachmentStore: attachmentStore,
            db: db,
            interactionStore: interactionStore,
        )

        let deviceManager = OWSDeviceManagerImpl()
        let deviceStore = OWSDeviceStore()
        let deviceService = OWSDeviceServiceImpl(
            db: db,
            deviceManager: deviceManager,
            deviceStore: deviceStore,
            identityManager: identityManager,
            messageSenderJobQueue: messageSenderJobQueue,
            networkManager: networkManager,
            recipientFetcher: recipientFetcher,
            recipientManager: recipientManager,
            threadStore: threadStore,
            tsAccountManager: tsAccountManager,
        )
        let inactiveLinkedDeviceFinder = InactiveLinkedDeviceFinderImpl(
            dateProvider: dateProvider,
            db: db,
            deviceService: deviceService,
            deviceStore: deviceStore,
            remoteConfigProvider: remoteConfigManager,
            tsAccountManager: tsAccountManager,
        )

        let linkAndSyncManager = LinkAndSyncManagerImpl(
            appContext: appContext,
            attachmentDownloadManager: attachmentDownloadManager,
            attachmentUploadManager: attachmentUploadManager,
            backupArchiveManager: backupArchiveManager,
            dateProvider: dateProvider,
            db: db,
            deviceSleepManager: deviceSleepManager,
            messagePipelineSupervisor: messagePipelineSupervisor,
            networkManager: networkManager,
            tsAccountManager: tsAccountManager,
        )

        let groupMessageProcessorManager = GroupMessageProcessorManager()

        let receiptSender = ReceiptSender(
            appReadiness: appReadiness,
            recipientDatabaseTable: recipientDatabaseTable,
        )

        let messageFetcherJob = MessageFetcherJob()

        let backgroundMessageFetcherFactory = BackgroundMessageFetcherFactory(
            attachmentBackfillManager: attachmentBackfillManager,
            chatConnectionManager: chatConnectionManager,
            groupMessageProcessorManager: groupMessageProcessorManager,
            messageProcessor: messageProcessor,
            messageSenderJobQueue: messageSenderJobQueue,
            receiptSender: receiptSender,
            storageServiceManager: storageServiceManager,
        )

        let backupExportJobRunner = BackupExportJobRunnerImpl(
            backupExportJob: BackupExportJob(
                accountKeyStore: accountKeyStore,
                backupArchiveManager: backupArchiveManager,
                backupAttachmentCoordinator: backupAttachmentCoordinator,
                backupAttachmentDownloadQueueStatusManager: backupAttachmentDownloadQueueStatusManager,
                backupAttachmentUploadProgress: backupAttachmentUploadProgress,
                backupAttachmentUploadQueueStatusManager: backupAttachmentUploadQueueStatusManager,
                backupExportJobStore: backupExportJobStore,
                backupSettingsStore: backupSettingsStore,
                db: db,
                messageProcessor: messageProcessor,
                reachabilityManager: reachabilityManager,
                tsAccountManager: tsAccountManager,
            ),
            backupExportJobStore: backupExportJobStore,
            db: db,
        )

        let backupFailureStateManager = BackupFailureStateManager(
            backupSettingsStore: backupSettingsStore,
            dateProvider: dateProvider,
            tsAccountManager: tsAccountManager,
        )

        let dependenciesBridge = DependenciesBridge(
            accountAttributesUpdater: accountAttributesUpdater,
            accountEntropyPoolManager: accountEntropyPoolManager,
            adHocCallRecordManager: adHocCallRecordManager,
            adminDeleteManager: adminDeleteManager,
            appExpiry: appExpiry,
            attachmentContentValidator: attachmentContentValidator,
            attachmentDownloadManager: attachmentDownloadManager,
            attachmentDownloadStore: attachmentDownloadStore,
            attachmentManager: attachmentManager,
            attachmentStore: attachmentStore,
            attachmentThumbnailService: attachmentThumbnailService,
            attachmentBackfillManager: attachmentBackfillManager,
            attachmentUploadManager: attachmentUploadManager,
            attachmentValidationBackfillMigrator: attachmentValidationBackfillMigrator,
            attachmentViewOnceManager: attachmentViewOnceManager,
            audioWaveformManager: audioWaveformManager,
            authorMergeHelper: authorMergeHelper,
            avatarDefaultColorManager: avatarDefaultColorManager,
            backgroundMessageFetcherFactory: backgroundMessageFetcherFactory,
            backupArchiveErrorPresenter: backupArchiveErrorPresenter,
            backupArchiveManager: backupArchiveManager,
            backupAttachmentDownloadProgress: backupAttachmentDownloadProgress,
            backupAttachmentDownloadStore: backupAttachmentDownloadStore,
            backupAttachmentDownloadQueueStatusManager: backupAttachmentDownloadQueueStatusManager,
            backupAttachmentCoordinator: backupAttachmentCoordinator,
            backupAttachmentUploadProgress: backupAttachmentUploadProgress,
            backupAttachmentUploadQueueStatusManager: backupAttachmentUploadQueueStatusManager,
            backupAttachmentUploadStore: backupAttachmentUploadStore,
            backupExportJobRunner: backupExportJobRunner,
            backupFailureStateManager: backupFailureStateManager,
            backupIdService: backupIdService,
            backupKeyService: backupKeyService,
            backupListMediaManager: backupListMediaManager,
            backupListMediaStore: backupListMediaStore,
            backupRequestManager: backupRequestManager,
            backupPlanManager: backupPlanManager,
            backupSubscriptionManager: backupSubscriptionManager,
            backupTestFlightEntitlementManager: backupTestFlightEntitlementManager,
            badgeCountFetcher: badgeCountFetcher,
            blockedRecipientStore: blockedRecipientStore,
            callLinkStore: callLinkStore,
            callRecordDeleteManager: callRecordDeleteManager,
            callRecordMissedCallManager: callRecordMissedCallManager,
            callRecordQuerier: callRecordQuerier,
            callRecordStore: callRecordStore,
            changePhoneNumberPniManager: changePhoneNumberPniManager,
            chatColorSettingStore: chatColorSettingStore,
            chatConnectionManager: chatConnectionManager,
            contactShareManager: contactShareManager,
            cron: cron,
            currentCallProvider: currentCallProvider,
            databaseChangeObserver: databaseStorage.databaseChangeObserver,
            db: db,
            deletedCallRecordExpirationJob: deletedCallRecordExpirationJob,
            deletedCallRecordStore: deletedCallRecordStore,
            deleteForMeIncomingSyncMessageManager: deleteForMeIncomingSyncMessageManager,
            deleteForMeOutgoingSyncMessageManager: deleteForMeOutgoingSyncMessageManager,
            deviceManager: deviceManager,
            deviceService: deviceService,
            deviceSleepManager: deviceSleepManager,
            deviceStore: deviceStore,
            disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore,
            disappearingMessagesExpirationJob: disappearingMessagesExpirationJob,
            donationReceiptCredentialResultStore: donationReceiptCredentialResultStore,
            editManager: editManager,
            editMessageStore: editMessageStore,
            externalPendingIDEALDonationStore: externalPendingIDEALDonationStore,
            groupCallRecordManager: groupCallRecordManager,
            groupMemberStore: groupMemberStore,
            groupMemberUpdater: groupMemberUpdater,
            groupSendEndorsementStore: groupSendEndorsementStore,
            groupUpdateInfoMessageInserter: groupUpdateInfoMessageInserter,
            identityKeyMismatchManager: identityKeyMismatchManager,
            identityManager: identityManager,
            inactiveLinkedDeviceFinder: inactiveLinkedDeviceFinder,
            inactivePrimaryDeviceStore: inactivePrimaryDeviceStore,
            incomingCallEventSyncMessageManager: incomingCallEventSyncMessageManager,
            incomingCallLogEventSyncMessageManager: incomingCallLogEventSyncMessageManager,
            incomingPniChangeNumberProcessor: incomingPniChangeNumberProcessor,
            individualCallRecordManager: individualCallRecordManager,
            interactionDeleteManager: interactionDeleteManager,
            interactionStore: interactionStore,
            keyTransparencyManager: keyTransparencyManager,
            lastVisibleInteractionStore: lastVisibleInteractionStore,
            linkAndSyncManager: linkAndSyncManager,
            linkPreviewManager: linkPreviewManager,
            linkPreviewSettingStore: linkPreviewSettingStore,
            linkPreviewSettingManager: linkPreviewSettingManager,
            accountKeyStore: accountKeyStore,
            localProfileChecker: localProfileChecker,
            localUsernameManager: localUsernameManager,
            masterKeySyncManager: masterKeySyncManager,
            mediaBandwidthPreferenceStore: mediaBandwidthPreferenceStore,
            messageStickerManager: messageStickerManager,
            nicknameManager: nicknameManager,
            orphanedAttachmentCleaner: orphanedAttachmentCleaner,
            archivedPaymentStore: archivedPaymentStore,
            phoneNumberDiscoverabilityManager: phoneNumberDiscoverabilityManager,
            phoneNumberVisibilityFetcher: phoneNumberVisibilityFetcher,
            pinnedMessageManager: pinnedMessageManager,
            pinnedMessageExpirationJob: pinnedMessageExpirationJob,
            pinnedThreadManager: pinnedThreadManager,
            pinnedThreadStore: pinnedThreadStore,
            pollMessageManager: pollMessageManager,
            preKeyManager: preKeyManager,
            privateStoryThreadDeletionManager: privateStoryThreadDeletionManager,
            quotedReplyManager: quotedReplyManager,
            reactionStore: reactionStore,
            recipientDatabaseTable: recipientDatabaseTable,
            recipientFetcher: recipientFetcher,
            recipientHidingManager: recipientHidingManager,
            recipientIdFinder: recipientIdFinder,
            recipientManager: recipientManager,
            recipientMerger: recipientMerger,
            registrationSessionManager: registrationSessionManager,
            registrationStateChangeManager: registrationStateChangeManager,
            searchableNameIndexer: searchableNameIndexer,
            sentMessageTranscriptReceiver: sentMessageTranscriptReceiver,
            signalProtocolStoreManager: signalProtocolStoreManager,
            storageServiceRecordIkmMigrator: storageServiceRecordIkmMigrator,
            storyMessageExpirationJob: storyMessageExpirationJob,
            storyRecipientManager: storyRecipientManager,
            storyRecipientStore: storyRecipientStore,
            subscriptionConfigManager: subscriptionConfigManager,
            svr: svr,
            svrCredentialStorage: svrCredentialStorage,
            svrLocalStorage: svrLocalStorage,
            threadAssociatedDataStore: threadAssociatedDataStore,
            threadRemover: threadRemover,
            threadReplyInfoStore: threadReplyInfoStore,
            threadSoftDeleteManager: threadSoftDeleteManager,
            threadStore: threadStore,
            tsAccountManager: tsAccountManager,
            usernameApiClient: usernameApiClient,
            usernameEducationManager: usernameEducationManager,
            usernameLinkManager: usernameLinkManager,
            usernameLookupManager: usernameLookupManager,
            usernameValidationManager: usernameValidationManager,
            wallpaperImageStore: wallpaperImageStore,
            wallpaperStore: wallpaperStore,
        )
        DependenciesBridge.setShared(dependenciesBridge, isRunningTests: appContext.isRunningTests)

        let proximityMonitoringManager = OWSProximityMonitoringManagerImpl()
        let avatarBuilder = AvatarBuilder(appReadiness: appReadiness)
        let smJobQueues = SignalMessagingJobQueues(
            appReadiness: appReadiness,
            db: db,
            reachabilityManager: reachabilityManager,
        )

        let pendingReceiptRecorder = testDependencies.pendingReceiptRecorder
            ?? MessageRequestPendingReceipts(appReadiness: appReadiness)
        let messageReceiver = MessageReceiver(
            callMessageHandler: callMessageHandler,
            deleteForMeSyncMessageReceiver: DeleteForMeSyncMessageReceiverImpl(
                deleteForMeIncomingSyncMessageManager: deleteForMeIncomingSyncMessageManager,
            ),
        )
        let messageDecrypter = OWSMessageDecrypter(appReadiness: appReadiness)
        let stickerManager = StickerManager(
            appReadiness: appReadiness,
            dateProvider: dateProvider,
        )
        let sskPreferences = SSKPreferences()
        let groupV2Updates = testDependencies.groupV2Updates ?? GroupV2UpdatesImpl(appReadiness: appReadiness)
        let paymentsCurrencies = testDependencies.paymentsCurrencies ?? PaymentsCurrenciesImpl(appReadiness: appReadiness)
        let spamChallengeResolver = SpamChallengeResolver(appReadiness: appReadiness)
        let phoneNumberUtil = PhoneNumberUtil()
        let contactDiscoveryManager = ContactDiscoveryManagerImpl(
            db: db,
            recipientDatabaseTable: recipientDatabaseTable,
            recipientFetcher: recipientFetcher,
            recipientManager: recipientManager,
            recipientMerger: recipientMerger,
            tsAccountManager: tsAccountManager,
            udManager: udManager,
            libsignalNet: libsignalNet,
        )
        let localUserLeaveGroupJobQueue = LocalUserLeaveGroupJobQueue(
            db: db,
            reachabilityManager: reachabilityManager,
        )
        let donationReceiptCredentialRedemptionJobQueue = DonationReceiptCredentialRedemptionJobQueue(
            dateProvider: dateProvider,
            db: db,
            donationReceiptCredentialResultStore: donationReceiptCredentialResultStore,
            networkManager: networkManager,
            profileManager: profileManager,
            reachabilityManager: reachabilityManager,
            tsAccountManager: tsAccountManager,
        )

        let groupCallPeekClient = GroupCallPeekClient(db: db, groupsV2: groupsV2)
        let groupCallManager = GroupCallManager(
            currentCallProvider: currentCallProvider,
            groupCallPeekClient: groupCallPeekClient,
        )

        let paymentsLock = OWSPaymentsLock(appReadiness: appReadiness)

        let sskEnvironment = SSKEnvironment(
            contactManager: contactManager,
            messageSender: messageSender,
            pendingReceiptRecorder: pendingReceiptRecorder,
            profileManager: profileManager,
            networkManager: networkManager,
            messageReceiver: messageReceiver,
            blockingManager: blockingManager,
            remoteConfigManager: remoteConfigManager,
            aciSignalProtocolStore: aciProtocolStore,
            pniSignalProtocolStore: pniProtocolStore,
            udManager: udManager,
            messageDecrypter: messageDecrypter,
            groupMessageProcessorManager: groupMessageProcessorManager,
            ows2FAManager: ows2FAManager,
            receiptManager: receiptManager,
            receiptSender: receiptSender,
            reachabilityManager: reachabilityManager,
            syncManager: syncManager,
            typingIndicators: typingIndicators,
            stickerManager: stickerManager,
            databaseStorage: databaseStorage,
            signalServiceAddressCache: signalServiceAddressCache,
            signalService: signalService,
            storageServiceManager: storageServiceManager,
            sskPreferences: sskPreferences,
            groupsV2: groupsV2,
            groupV2Updates: groupV2Updates,
            messageFetcherJob: messageFetcherJob,
            versionedProfiles: versionedProfiles,
            modelReadCaches: modelReadCaches,
            earlyMessageManager: earlyMessageManager,
            messagePipelineSupervisor: messagePipelineSupervisor,
            appExpiry: appExpiry,
            messageProcessor: messageProcessor,
            paymentsHelper: paymentsHelper,
            paymentsCurrencies: paymentsCurrencies,
            paymentsEvents: paymentsEvents,
            paymentsLock: paymentsLock,
            mobileCoinHelper: mobileCoinHelper,
            spamChallengeResolver: spamChallengeResolver,
            senderKeyStore: senderKeyStore,
            phoneNumberUtil: phoneNumberUtil,
            webSocketFactory: webSocketFactory,
            systemStoryManager: systemStoryManager,
            contactDiscoveryManager: contactDiscoveryManager,
            notificationPresenter: notificationPresenter,
            messageSendLog: messageSendLog,
            messageSenderJobQueue: messageSenderJobQueue,
            localUserLeaveGroupJobQueue: localUserLeaveGroupJobQueue,
            callRecordDeleteAllJobQueue: callRecordDeleteAllJobQueue,
            bulkDeleteInteractionJobQueue: bulkDeleteInteractionJobQueue,
            donationReceiptCredentialRedemptionJobQueue: donationReceiptCredentialRedemptionJobQueue,
            preferences: preferences,
            proximityMonitoringManager: proximityMonitoringManager,
            avatarBuilder: avatarBuilder,
            smJobQueues: smJobQueues,
            groupCallManager: groupCallManager,
            profileFetcher: profileFetcher,
        )
        SSKEnvironment.setShared(sskEnvironment, isRunningTests: appContext.isRunningTests)

        // Register renamed classes.
        NSKeyedUnarchiver.setClass(OWSUserProfile.self, forClassName: "OWSUserProfile")
        NSKeyedUnarchiver.setClass(TSGroupModelV2.self, forClassName: "TSGroupModelV2")
        NSKeyedUnarchiver.setClass(PendingProfileUpdate.self, forClassName: "SignalMessaging.PendingProfileUpdate")

        Sounds.performStartupTasks(appReadiness: appReadiness)

        return AppSetup.DataMigrationContinuation(
            appContext: appContext,
            appReadiness: appReadiness,
            authCredentialStore: authCredentialStore,
            dependenciesBridge: dependenciesBridge,
            libsignalNet: libsignalNet,
            sskEnvironment: sskEnvironment,
            backgroundTask: backgroundTask,
            authCredentialManager: authCredentialManager,
            callLinkPublicParams: callLinkPublicParams,
            remoteConfigManager: remoteConfigManager,
        )
    }

    private func configureUnsatisfiableConstraintLogging() {
        UserDefaults.standard.setValue(DebugFlags.internalLogging, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
    }
}

// MARK: - DataMigrationContinuation

extension AppSetup {
    public class DataMigrationContinuation {
        fileprivate let appContext: AppContext
        fileprivate let appReadiness: AppReadiness
        fileprivate let authCredentialStore: AuthCredentialStore
        public let dependenciesBridge: DependenciesBridge
        fileprivate let libsignalNet: Net
        fileprivate let remoteConfigManager: RemoteConfigManager
        public let sskEnvironment: SSKEnvironment
        fileprivate let backgroundTask: OWSBackgroundTask

        // We need this in AppDelegate, but it doesn't need to be a global/attached
        // to DependenciesBridge.shared. We'll need some mechanism for this in the
        // future, but for now, just put it here so it's part of the result.
        public let authCredentialManager: any AuthCredentialManager
        public let callLinkPublicParams: GenericServerPublicParams

        fileprivate init(
            appContext: AppContext,
            appReadiness: AppReadiness,
            authCredentialStore: AuthCredentialStore,
            dependenciesBridge: DependenciesBridge,
            libsignalNet: Net,
            sskEnvironment: SSKEnvironment,
            backgroundTask: OWSBackgroundTask,
            authCredentialManager: any AuthCredentialManager,
            callLinkPublicParams: GenericServerPublicParams,
            remoteConfigManager: RemoteConfigManager,
        ) {
            self.appContext = appContext
            self.appReadiness = appReadiness
            self.authCredentialStore = authCredentialStore
            self.dependenciesBridge = dependenciesBridge
            self.libsignalNet = libsignalNet
            self.sskEnvironment = sskEnvironment
            self.backgroundTask = backgroundTask
            self.authCredentialManager = authCredentialManager
            self.callLinkPublicParams = callLinkPublicParams
            self.remoteConfigManager = remoteConfigManager
        }
    }
}

extension AppSetup.DataMigrationContinuation {
    public func migrateDatabaseData() async -> AppSetup.FinalContinuation {
        let databaseStorage = sskEnvironment.databaseStorageRef

        databaseStorage.runGrdbDataMigrations()
        do {
            try databaseStorage.grdbStorage.setupDatabaseChangeObserver()
        } catch {
            owsFail("Couldn't set up change observer: \(error.grdbErrorForLogging)")
        }

        self.backgroundTask.end()
        return AppSetup.FinalContinuation(
            appContext: self.appContext,
            appReadiness: self.appReadiness,
            authCredentialStore: self.authCredentialStore,
            dependenciesBridge: self.dependenciesBridge,
            libsignalNet: self.libsignalNet,
            sskEnvironment: self.sskEnvironment,
        )
    }
}

// MARK: - FinalContinuation

extension AppSetup {
    public class FinalContinuation {
        private let appContext: AppContext
        private let appReadiness: AppReadiness
        private let authCredentialStore: AuthCredentialStore
        public let dependenciesBridge: DependenciesBridge
        private let libsignalNet: Net
        private let sskEnvironment: SSKEnvironment

        @MainActor private var didRunLaunchTasks = false

        fileprivate init(
            appContext: AppContext,
            appReadiness: AppReadiness,
            authCredentialStore: AuthCredentialStore,
            dependenciesBridge: DependenciesBridge,
            libsignalNet: Net,
            sskEnvironment: SSKEnvironment,
        ) {
            self.appContext = appContext
            self.appReadiness = appReadiness
            self.authCredentialStore = authCredentialStore
            self.dependenciesBridge = dependenciesBridge
            self.libsignalNet = libsignalNet
            self.sskEnvironment = sskEnvironment
        }
    }
}

extension AppSetup.FinalContinuation {
    public enum SetupError: Error {
        case corruptRegistrationState
    }

    @MainActor
    public func runLaunchTasksIfNeededAndReloadCaches() {

        if !self.didRunLaunchTasks {
            // Coders are consulted in reverse order of adding, so add
            // the AWebPCoder last, which used the native ImageIO, if supported
            SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)
            SDImageCodersManager.shared.addCoder(SDImageAWebPCoder.shared)
        }

        if self.didRunLaunchTasks {
            let remoteConfig = sskEnvironment.databaseStorageRef.read { tx in
                dependenciesBridge.tsAccountManager.warmCaches(tx: tx)
                return sskEnvironment.remoteConfigManagerRef.warmCaches(tx: tx)
            }
            libsignalNet.setRemoteConfig(remoteConfig.netConfig(), buildVariant: BuildFlags.netBuildVariant)
        }

        // Warm (or re-warm) all of the caches. In theory, every cache is
        // susceptible to diverging state between the Main App & NSE and should be
        // reloaded here. In practice, some caches exist but aren't used by the
        // NSE, or they are used but behave properly even if they're not reloaded.
        self.sskEnvironment.warmCaches(appReadiness: self.appReadiness, dependenciesBridge: self.dependenciesBridge)

        self.appReadiness.runNowOrWhenAppWillBecomeReady {
            self.dependenciesBridge.chatConnectionManager.updateCanOpenWebSocket()
        }

        self.appReadiness.runNowOrWhenAppDidBecomeReadySync {
            self.dependenciesBridge.appExpiry.refreshExpirationTimer()
        }

        if self.didRunLaunchTasks {
            return
        }
        self.didRunLaunchTasks = true

        // See above.
        self.sskEnvironment.signalServiceAddressCacheRef.prepareCache()

        ZkParamsMigrator(
            appReadiness: appReadiness,
            authCredentialStore: authCredentialStore,
            db: dependenciesBridge.db,
            profileManager: sskEnvironment.profileManagerRef,
            tsAccountManager: dependenciesBridge.tsAccountManager,
            versionedProfiles: sskEnvironment.versionedProfilesRef,
        ).migrateIfNeeded()

        appReadiness.runNowOrWhenAppDidBecomeReadyAsync { [appContext, dependenciesBridge, sskEnvironment] in
            sskEnvironment.localUserLeaveGroupJobQueueRef.start(appContext: appContext)
            sskEnvironment.callRecordDeleteAllJobQueueRef.start(appContext: appContext)
            sskEnvironment.bulkDeleteInteractionJobQueueRef.start(appContext: appContext)
            sskEnvironment.donationReceiptCredentialRedemptionJobQueue.start(appContext: appContext)
            sskEnvironment.smJobQueuesRef.incomingContactSyncJobQueue.start(appContext: appContext)
            sskEnvironment.smJobQueuesRef.sendGiftBadgeJobQueue.start(appContext: appContext)

            let preKeyManager = dependenciesBridge.preKeyManager
            Task {
                // Rotate ACI keys first since PNI keys may block on incoming messages.
                // TODO: Don't block ACI operations if PNI operations are blocked.
                try await preKeyManager.rotatePreKeysOnUpgradeIfNecessary(for: .aci)
                try await preKeyManager.rotatePreKeysOnUpgradeIfNecessary(for: .pni)
            }
        }
    }

    @MainActor
    public func setUpLocalIdentifiers(
        willResumeInProgressRegistration: Bool,
        canInitiateRegistration: Bool,
    ) -> SetupError? {
        let storageServiceManager = sskEnvironment.storageServiceManagerRef
        let tsAccountManager = dependenciesBridge.tsAccountManager

        let registrationState = tsAccountManager.registrationStateWithMaybeSneakyTransaction
        let canInitiateReregistration = registrationState.isDeregistered && canInitiateRegistration

        if registrationState.isRegistered {
            // TODO: Enforce already-true invariant "registered means LocalIdentifiers" via the compiler.
            let localIdentifiers = tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!
            storageServiceManager.setLocalIdentifiers(localIdentifiers)
        } else if !willResumeInProgressRegistration, !canInitiateReregistration {
            // We aren't registered, and we're not in the middle of registration, so
            // throw an error about corrupt registration.
            return .corruptRegistrationState
        }

        if !willResumeInProgressRegistration, !canInitiateReregistration {
            // We are fully registered, and we're not in the middle of registration, so
            // ensure discoverability is configured.
            setUpDefaultDiscoverability()
        }

        return nil
    }

    private func setUpDefaultDiscoverability() {
        let databaseStorage = sskEnvironment.databaseStorageRef
        let phoneNumberDiscoverabilityManager = DependenciesBridge.shared.phoneNumberDiscoverabilityManager
        let tsAccountManager = DependenciesBridge.shared.tsAccountManager

        if databaseStorage.read(block: { tsAccountManager.phoneNumberDiscoverability(tx: $0) }) != nil {
            return
        }

        databaseStorage.write { tx in
            phoneNumberDiscoverabilityManager.setPhoneNumberDiscoverability(
                PhoneNumberDiscoverabilityManager.Constants.discoverabilityDefault,
                updateAccountAttributes: true,
                updateStorageService: true,
                authedAccount: .implicit(),
                tx: tx,
            )
        }
    }
}
