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

import Foundation
import SignalServiceKit

class NSEEnvironment {
    let appReadiness: AppReadinessSetter
    let appContext: NSEContext

    init() {
        self.appContext = NSEContext()
        SetCurrentAppContext(self.appContext, isRunningTests: false)
        appReadiness = AppReadinessImpl()
    }

    // MARK: - Setup

    @MainActor private var didStartAppSetup = false
    @MainActor private var finalContinuation: AppSetup.FinalContinuation?

    /// Called for each notification the NSE receives.
    ///
    /// Will be invoked multiple times in the same NSE process.
    @MainActor
    func setUp(logger: NSELogger) {
        let debugLogger = DebugLogger.shared

        if !didStartAppSetup {
            debugLogger.enableFileLogging(appContext: appContext, canLaunchInBackground: true)
            debugLogger.enableTTYLoggingIfNeeded()
            DebugLogger.registerLibsignal()
            DebugLogger.registerRingRTC(appContext: appContext)
            didStartAppSetup = true
        }

        logger.info("pid: \(ProcessInfo.processInfo.processIdentifier), memoryUsage: \(LocalDevice.memoryUsageString)")
        logger.flush()
    }

    @MainActor
    func setUpDatabase(logger: NSELogger) async throws -> AppSetup.FinalContinuation {
        if let finalContinuation {
            return finalContinuation
        }

        let keychainStorage = KeychainStorageImpl(isUsingProductionService: TSConstants.isUsingProductionService)
        let databaseStorage = try SDSDatabaseStorage(
            appReadiness: appReadiness,
            databaseFileUrl: SDSDatabaseStorage.grdbDatabaseFileUrl,
            keychainStorage: keychainStorage,
        )
        databaseStorage.grdbStorage.setUpDatabasePathKVO()

        let finalContinuation = await AppSetup()
            .start(
                appContext: appContext,
                databaseStorage: databaseStorage,
            )
            .migrateDatabaseSchema()
            .initGlobals(
                appContext: appContext,
                appReadiness: appReadiness,
                backupArchiveErrorPresenterFactory: NoOpBackupArchiveErrorPresenterFactory(),
                deviceBatteryLevelManager: nil,
                deviceSleepManager: nil,
                paymentsEvents: PaymentsEventsAppExtension(),
                mobileCoinHelper: MobileCoinHelperMinimal(),
                callMessageHandler: NSECallMessageHandler(),
                currentCallProvider: CurrentCallNoOpProvider(),
                notificationPresenter: NotificationPresenterImpl(),
            )
            .migrateDatabaseData()

        self.finalContinuation = finalContinuation
        return finalContinuation
    }

    @MainActor
    func setAppIsReady() {
        if appReadiness.isAppReady {
            return
        }

        // Note that this does much more than set a flag; it will also run all deferred blocks.
        appReadiness.setAppIsReady()

        let appVersion = AppVersionImpl.shared
        appVersion.dumpToLog()
        appVersion.updateFirstVersionIfNeeded()
        appVersion.nseLaunchDidComplete()
    }
}
