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

import Foundation

public class SSKEnvironment: NSObject {

    private static var _shared: SSKEnvironment?

    public static var hasShared: Bool { _shared != nil }

    @objc
    public static var shared: SSKEnvironment { _shared! }

    public static func setShared(_ env: SSKEnvironment, isRunningTests: Bool) {
        owsAssert(_shared == nil || isRunningTests)
        _shared = env
    }

    #if TESTABLE_BUILD
    private(set) public var contactManagerRef: any ContactManager
    private(set) public var messageSenderRef: MessageSender
    private(set) public var networkManagerRef: NetworkManager
    private(set) public var paymentsHelperRef: PaymentsHelperSwift
    private(set) public var groupsV2Ref: GroupsV2
    #else
    public let contactManagerRef: any ContactManager
    public let messageSenderRef: MessageSender
    public let networkManagerRef: NetworkManager
    public let paymentsHelperRef: PaymentsHelperSwift
    public let groupsV2Ref: GroupsV2
    #endif

    public let pendingReceiptRecorderRef: PendingReceiptRecorder
    public let profileManagerRef: ProfileManager
    public let messageReceiverRef: MessageReceiver
    public let blockingManagerRef: BlockingManager
    public let remoteConfigManagerRef: RemoteConfigManager
    public let udManagerRef: OWSUDManager
    public let messageDecrypterRef: OWSMessageDecrypter
    public let groupsV2MessageProcessorRef: GroupsV2MessageProcessor
    public let ows2FAManagerRef: OWS2FAManager
    public let disappearingMessagesJobRef: OWSDisappearingMessagesJob
    public let receiptManagerRef: OWSReceiptManager
    @objc
    public let receiptSenderRef: ReceiptSender
    public let reachabilityManagerRef: SSKReachabilityManager
    public let syncManagerRef: SyncManagerProtocol
    public let typingIndicatorsRef: TypingIndicators
    public let stickerManagerRef: StickerManager
    public let databaseStorageRef: SDSDatabaseStorage
    public let signalServiceAddressCacheRef: SignalServiceAddressCache
    public let signalServiceRef: OWSSignalServiceProtocol
    public let accountServiceClientRef: AccountServiceClient
    public let storageServiceManagerRef: StorageServiceManager
    public let sskPreferencesRef: SSKPreferences
    public let groupV2UpdatesRef: GroupV2Updates
    public let messageFetcherJobRef: MessageFetcherJob
    public let versionedProfilesRef: VersionedProfilesSwift
    public let modelReadCachesRef: ModelReadCaches
    public let earlyMessageManagerRef: EarlyMessageManager
    public let messagePipelineSupervisorRef: MessagePipelineSupervisor
    public let messageProcessorRef: MessageProcessor
    public let paymentsCurrenciesRef: PaymentsCurrenciesSwift
    public let paymentsEventsRef: PaymentsEvents
    public let mobileCoinHelperRef: MobileCoinHelper
    public let spamChallengeResolverRef: SpamChallengeResolver
    public let senderKeyStoreRef: SenderKeyStore
    public let phoneNumberUtilRef: PhoneNumberUtil
    public let webSocketFactoryRef: WebSocketFactory
    public let legacyChangePhoneNumberRef: LegacyChangePhoneNumber
    public let subscriptionManagerRef: SubscriptionManager
    public let systemStoryManagerRef: SystemStoryManagerProtocol
    public let contactDiscoveryManagerRef: ContactDiscoveryManager
    public let notificationPresenterRef: any NotificationPresenter
    public let messageSendLogRef: MessageSendLog
    public let messageSenderJobQueueRef: MessageSenderJobQueue
    public let localUserLeaveGroupJobQueueRef: LocalUserLeaveGroupJobQueue
    public let callRecordDeleteAllJobQueueRef: CallRecordDeleteAllJobQueue
    public let bulkDeleteInteractionJobQueueRef: BulkDeleteInteractionJobQueue
    public let preferencesRef: Preferences
    public let proximityMonitoringManagerRef: OWSProximityMonitoringManager
    public let avatarBuilderRef: AvatarBuilder
    public let smJobQueuesRef: SignalMessagingJobQueues
    public let groupCallManagerRef: GroupCallManager
    public let profileFetcherRef: any ProfileFetcher

    private let appExpiryRef: AppExpiry
    private let aciSignalProtocolStoreRef: SignalProtocolStore
    private let pniSignalProtocolStoreRef: SignalProtocolStore

    public init(
        contactManager: any ContactManager,
        messageSender: MessageSender,
        pendingReceiptRecorder: PendingReceiptRecorder,
        profileManager: ProfileManager,
        networkManager: NetworkManager,
        messageReceiver: MessageReceiver,
        blockingManager: BlockingManager,
        remoteConfigManager: RemoteConfigManager,
        aciSignalProtocolStore: SignalProtocolStore,
        pniSignalProtocolStore: SignalProtocolStore,
        udManager: OWSUDManager,
        messageDecrypter: OWSMessageDecrypter,
        groupsV2MessageProcessor: GroupsV2MessageProcessor,
        ows2FAManager: OWS2FAManager,
        disappearingMessagesJob: OWSDisappearingMessagesJob,
        receiptManager: OWSReceiptManager,
        receiptSender: ReceiptSender,
        reachabilityManager: SSKReachabilityManager,
        syncManager: SyncManagerProtocol,
        typingIndicators: TypingIndicators,
        stickerManager: StickerManager,
        databaseStorage: SDSDatabaseStorage,
        signalServiceAddressCache: SignalServiceAddressCache,
        signalService: OWSSignalServiceProtocol,
        accountServiceClient: AccountServiceClient,
        storageServiceManager: StorageServiceManager,
        sskPreferences: SSKPreferences,
        groupsV2: GroupsV2,
        groupV2Updates: GroupV2Updates,
        messageFetcherJob: MessageFetcherJob,
        versionedProfiles: VersionedProfilesSwift,
        modelReadCaches: ModelReadCaches,
        earlyMessageManager: EarlyMessageManager,
        messagePipelineSupervisor: MessagePipelineSupervisor,
        appExpiry: AppExpiry,
        messageProcessor: MessageProcessor,
        paymentsHelper: PaymentsHelperSwift,
        paymentsCurrencies: PaymentsCurrenciesSwift,
        paymentsEvents: PaymentsEvents,
        mobileCoinHelper: MobileCoinHelper,
        spamChallengeResolver: SpamChallengeResolver,
        senderKeyStore: SenderKeyStore,
        phoneNumberUtil: PhoneNumberUtil,
        webSocketFactory: WebSocketFactory,
        legacyChangePhoneNumber: LegacyChangePhoneNumber,
        subscriptionManager: SubscriptionManager,
        systemStoryManager: SystemStoryManagerProtocol,
        contactDiscoveryManager: ContactDiscoveryManager,
        notificationPresenter: any NotificationPresenter,
        messageSendLog: MessageSendLog,
        messageSenderJobQueue: MessageSenderJobQueue,
        localUserLeaveGroupJobQueue: LocalUserLeaveGroupJobQueue,
        callRecordDeleteAllJobQueue: CallRecordDeleteAllJobQueue,
        bulkdDeleteInteractionJobQueue: BulkDeleteInteractionJobQueue,
        preferences: Preferences,
        proximityMonitoringManager: OWSProximityMonitoringManager,
        avatarBuilder: AvatarBuilder,
        smJobQueues: SignalMessagingJobQueues,
        groupCallManager: GroupCallManager,
        profileFetcher: any ProfileFetcher
    ) {
        self.contactManagerRef = contactManager
        self.messageSenderRef = messageSender
        self.pendingReceiptRecorderRef = pendingReceiptRecorder
        self.profileManagerRef = profileManager
        self.networkManagerRef = networkManager
        self.messageReceiverRef = messageReceiver
        self.blockingManagerRef = blockingManager
        self.remoteConfigManagerRef = remoteConfigManager
        self.aciSignalProtocolStoreRef = aciSignalProtocolStore
        self.pniSignalProtocolStoreRef = pniSignalProtocolStore
        self.udManagerRef = udManager
        self.messageDecrypterRef = messageDecrypter
        self.groupsV2MessageProcessorRef = groupsV2MessageProcessor
        self.ows2FAManagerRef = ows2FAManager
        self.disappearingMessagesJobRef = disappearingMessagesJob
        self.receiptManagerRef = receiptManager
        self.receiptSenderRef = receiptSender
        self.syncManagerRef = syncManager
        self.reachabilityManagerRef = reachabilityManager
        self.typingIndicatorsRef = typingIndicators
        self.stickerManagerRef = stickerManager
        self.databaseStorageRef = databaseStorage
        self.signalServiceAddressCacheRef = signalServiceAddressCache
        self.signalServiceRef = signalService
        self.accountServiceClientRef = accountServiceClient
        self.storageServiceManagerRef = storageServiceManager
        self.sskPreferencesRef = sskPreferences
        self.groupsV2Ref = groupsV2
        self.groupV2UpdatesRef = groupV2Updates
        self.messageFetcherJobRef = messageFetcherJob
        self.versionedProfilesRef = versionedProfiles
        self.modelReadCachesRef = modelReadCaches
        self.earlyMessageManagerRef = earlyMessageManager
        self.messagePipelineSupervisorRef = messagePipelineSupervisor
        self.appExpiryRef = appExpiry
        self.messageProcessorRef = messageProcessor
        self.paymentsHelperRef = paymentsHelper
        self.paymentsCurrenciesRef = paymentsCurrencies
        self.paymentsEventsRef = paymentsEvents
        self.mobileCoinHelperRef = mobileCoinHelper
        self.spamChallengeResolverRef = spamChallengeResolver
        self.senderKeyStoreRef = senderKeyStore
        self.phoneNumberUtilRef = phoneNumberUtil
        self.webSocketFactoryRef = webSocketFactory
        self.legacyChangePhoneNumberRef = legacyChangePhoneNumber
        self.subscriptionManagerRef = subscriptionManager
        self.systemStoryManagerRef = systemStoryManager
        self.contactDiscoveryManagerRef = contactDiscoveryManager
        self.notificationPresenterRef = notificationPresenter
        self.messageSendLogRef = messageSendLog
        self.messageSenderJobQueueRef = messageSenderJobQueue
        self.localUserLeaveGroupJobQueueRef = localUserLeaveGroupJobQueue
        self.callRecordDeleteAllJobQueueRef = callRecordDeleteAllJobQueue
        self.bulkDeleteInteractionJobQueueRef = bulkdDeleteInteractionJobQueue
        self.preferencesRef = preferences
        self.proximityMonitoringManagerRef = proximityMonitoringManager
        self.avatarBuilderRef = avatarBuilder
        self.smJobQueuesRef = smJobQueues
        self.groupCallManagerRef = groupCallManager
        self.profileFetcherRef = profileFetcher
    }

    public func signalProtocolStoreRef(for identity: OWSIdentity) -> SignalProtocolStore {
        switch identity {
        case .aci:
            return aciSignalProtocolStoreRef
        case .pni:
            return pniSignalProtocolStoreRef
        }
    }

    public static let warmCachesNotification = Notification.Name("WarmCachesNotification")

    func warmCaches() {
        SignalProxy.warmCaches()
        DependenciesBridge.shared.tsAccountManager.warmCaches()
        fixLocalRecipientIfNeeded()
        signalServiceAddressCache.warmCaches()
        signalService.warmCaches()
        remoteConfigManager.warmCaches()
        blockingManager.warmCaches()
        profileManager.warmCaches()
        receiptManager.prepareCachedValues()
        DependenciesBridge.shared.svr.warmCaches()
        typingIndicatorsImpl.warmCaches()
        paymentsHelper.warmCaches()
        paymentsCurrencies.warmCaches()
        StoryManager.setup()
        DependenciesBridge.shared.deviceManager.warmCaches()
        DependenciesBridge.shared.db.read { tx in appExpiryRef.warmCaches(with: tx) }

        AppReadiness.runNowOrWhenAppDidBecomeReadyAsync {
            self.localUserLeaveGroupJobQueueRef.start(appContext: CurrentAppContext())
            self.callRecordDeleteAllJobQueueRef.start(appContext: CurrentAppContext())
            self.bulkDeleteInteractionJobQueueRef.start(appContext: CurrentAppContext())
            self.smJobQueuesRef.tsAttachmentMultisendJobQueue.start(appContext: CurrentAppContext())
            self.smJobQueuesRef.incomingContactSyncJobQueue.start(appContext: CurrentAppContext())
            self.smJobQueuesRef.receiptCredentialJobQueue.start(appContext: CurrentAppContext())
            self.smJobQueuesRef.sendGiftBadgeJobQueue.start(appContext: CurrentAppContext())
            self.smJobQueuesRef.sessionResetJobQueue.start(appContext: CurrentAppContext())
        }

        NotificationCenter.default.post(name: SSKEnvironment.warmCachesNotification, object: nil)
    }

    /// Ensures the local SignalRecipient is correct.
    ///
    /// This primarily serves to ensure the local SignalRecipient has its own
    /// Pni (a one-time migration), but it also helps ensure that the value is
    /// always consistent with TSAccountManager's values.
    private func fixLocalRecipientIfNeeded() {
        databaseStorage.write { tx in
            guard let localIdentifiers = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx.asV2Read) else {
                return  // Not registered yet.
            }
            guard let phoneNumber = E164(localIdentifiers.phoneNumber) else {
                return  // Registered with an invalid phone number.
            }
            let recipientMerger = DependenciesBridge.shared.recipientMerger
            _ = recipientMerger.applyMergeForLocalAccount(
                aci: localIdentifiers.aci,
                phoneNumber: phoneNumber,
                pni: localIdentifiers.pni,
                tx: tx.asV2Write
            )
        }
    }

    #if TESTABLE_BUILD

    public func setContactManagerForUnitTests(_ contactManager: any ContactManager) {
        self.contactManagerRef = contactManager
    }

    public func setMessageSenderForUnitTests(_ messageSender: MessageSender) {
        self.messageSenderRef = messageSender
    }

    public func setNetworkManagerForUnitTests(_ networkManager: NetworkManager) {
        self.networkManagerRef = networkManager
    }

    public func setPaymentsHelperForUnitTests(_ paymentsHelper: PaymentsHelperSwift) {
        self.paymentsHelperRef = paymentsHelper
    }

    public func setGroupsV2ForUnitTests(_ groupsV2: GroupsV2) {
        self.groupsV2Ref = groupsV2
    }

    #endif
}
